ObjectStackObjectStack

Protocol schema — API Protocol reference

Protocol schemas of the ObjectStack API Protocol: AiAgentCapabilities and 136 more — each property with its type, default and a TypeScript example.

Source: packages/spec/src/api/protocol.zod.ts

TypeScript Usage

import { AiAgentCapabilitiesSchema, AiAgentChatRequestSchema, AiAgentSummarySchema, AiAgentsResponseSchema, AiChatRequestSchema, AiChatResponseSchema, AiCompleteRequestSchema, AiConversationSchema, AiMessageSchema, AiModelsResponseSchema, AiPendingActionSchema, AiPendingActionStatusSchema, AiStreamChunkSchema, ApproveAiPendingActionResponseSchema, AuditMetaItemRequestSchema, AuditMetaItemResponseSchema, AutomationActionsResponseSchema, AutomationTriggerRequestSchema, AutomationTriggerResponseSchema, BatchDataRequestSchema, BatchDataResponseSchema, CheckPermissionRequestSchema, CheckPermissionResponseSchema, CloneDataResponseSchema, CreateAiConversationRequestSchema, CreateDataRequestSchema, CreateDataResponseSchema, CreateManyDataRequestSchema, CreateManyDataResponseSchema, DeleteDataRequestSchema, DeleteDataResponseSchema, DeleteManyDataRequestSchema, DeleteManyDataResponseSchema, DeleteMetaItemRequestSchema, DeleteMetaItemResponseSchema, DiffMetaItemResponseSchema, DisablePackageRequestSchema, DisablePackageResponseSchema, EnablePackageRequestSchema, EnablePackageResponseSchema, FindDataRequestSchema, FindDataResponseSchema, FindReferencesToMetaResponseSchema, FlowRegistrationSchema, FlowRegistrationRefusalSchema, GetDataRequestSchema, GetDataResponseSchema, GetDiscoveryRequestSchema, GetDiscoveryResponseSchema, GetEffectivePermissionsRequestSchema, GetEffectivePermissionsResponseSchema, GetFieldLabelsRequestSchema, GetFieldLabelsResponseSchema, GetLocalesRequestSchema, GetLocalesResponseSchema, GetMetaDiagnosticsResponseSchema, GetMetaItemCachedRequestSchema, GetMetaItemCachedResponseSchema, GetMetaItemLayeredRequestSchema, GetMetaItemLayeredResponseSchema, GetMetaItemRequestSchema, GetMetaItemResponseSchema, GetMetaItemsRequestSchema, GetMetaItemsResponseSchema, GetMetaTypesRequestSchema, GetMetaTypesResponseSchema, GetNotificationPreferencesRequestSchema, GetNotificationPreferencesResponseSchema, GetObjectPermissionsRequestSchema, GetObjectPermissionsResponseSchema, GetPackageRequestSchema, GetPackageResponseSchema, GetPresenceRequestSchema, GetPresenceResponseSchema, GetPublishedMetaItemResponseSchema, GetTranslationsRequestSchema, GetTranslationsResponseSchema, GetUiViewRequestSchema, GetUiViewResponseSchema, HistoryMetaItemRequestSchema, HistoryMetaItemResponseSchema, HttpFindQueryParamsSchema, InstallPackageRequestSchema, InstallPackageResponseSchema, ListAiConversationsRequestSchema, ListAiConversationsResponseSchema, ListAiPendingActionsRequestSchema, ListAiPendingActionsResponseSchema, ListDraftsResponseSchema, ListNotificationsRequestSchema, ListNotificationsResponseSchema, ListPackagesRequestSchema, ListPackagesResponseSchema, MarkAllNotificationsReadRequestSchema, MarkAllNotificationsReadResponseSchema, MarkNotificationsReadRequestSchema, MarkNotificationsReadResponseSchema, MetadataConflictErrorSchema, NotificationSchema, NotificationPreferencesSchema, PublishMetaItemRequestSchema, PublishMetaItemResponseSchema, PublishPackageDraftsResponseSchema, RealtimeConnectRequestSchema, RealtimeConnectResponseSchema, RealtimeDisconnectRequestSchema, RealtimeDisconnectResponseSchema, RealtimeSubscribeRequestSchema, RealtimeSubscribeResponseSchema, RealtimeUnsubscribeRequestSchema, RealtimeUnsubscribeResponseSchema, RegisterDeviceRequestSchema, RegisterDeviceResponseSchema, RejectAiPendingActionResponseSchema, RollbackMetaItemResponseSchema, RuntimeAuthoringIssueSchema, SaveMetaItemRequestSchema, SaveMetaItemResponseSchema, SearchAllHitSchema, SearchAllPageHitSchema, SearchAllResponseSchema, SetPresenceRequestSchema, SetPresenceResponseSchema, UninstallPackageRequestSchema, UninstallPackageResponseSchema, UnregisterDeviceRequestSchema, UnregisterDeviceResponseSchema, UpdateAiConversationRequestSchema, UpdateDataRequestSchema, UpdateDataResponseSchema, UpdateManyDataRequestSchema, UpdateManyDataResponseSchema, UpdateNotificationPreferencesRequestSchema, UpdateNotificationPreferencesResponseSchema, ValidateDataIssueSchema, ValidateDataRequestSchema, ValidateDataResponseSchema } from '@objectstack/spec/api';
import type { AiAgentCapabilities, AiAgentChatRequest, AiAgentSummary, AiAgentsResponse, AiChatRequest, AiChatResponse, AiCompleteRequest, AiConversation, AiMessage, AiModelsResponse, AiPendingAction, AiPendingActionStatus, AiStreamChunk, ApproveAiPendingActionResponse, AuditMetaItemRequest, AuditMetaItemResponse, AutomationActionsResponse, AutomationTriggerRequest, AutomationTriggerResponse, BatchDataRequest, BatchDataResponse, CheckPermissionRequest, CheckPermissionResponse, CloneDataResponse, CreateAiConversationRequest, CreateDataRequest, CreateDataResponse, CreateManyDataRequest, CreateManyDataResponse, DeleteDataRequest, DeleteDataResponse, DeleteManyDataRequest, DeleteManyDataResponse, DeleteMetaItemRequest, DeleteMetaItemResponse, DiffMetaItemResponse, DisablePackageRequest, DisablePackageResponse, EnablePackageRequest, EnablePackageResponse, FindDataRequest, FindDataResponse, FindReferencesToMetaResponse, FlowRegistration, FlowRegistrationRefusal, GetDataRequest, GetDataResponse, GetDiscoveryRequest, GetDiscoveryResponse, GetEffectivePermissionsRequest, GetEffectivePermissionsResponse, GetFieldLabelsRequest, GetFieldLabelsResponse, GetLocalesRequest, GetLocalesResponse, GetMetaDiagnosticsResponse, GetMetaItemCachedRequest, GetMetaItemCachedResponse, GetMetaItemLayeredRequest, GetMetaItemLayeredResponse, GetMetaItemRequest, GetMetaItemResponse, GetMetaItemsRequest, GetMetaItemsResponse, GetMetaTypesRequest, GetMetaTypesResponse, GetNotificationPreferencesRequest, GetNotificationPreferencesResponse, GetObjectPermissionsRequest, GetObjectPermissionsResponse, GetPackageRequest, GetPackageResponse, GetPresenceRequest, GetPresenceResponse, GetPublishedMetaItemResponse, GetTranslationsRequest, GetTranslationsResponse, GetUiViewRequest, GetUiViewResponse, HistoryMetaItemRequest, HistoryMetaItemResponse, InstallPackageRequest, InstallPackageResponse, ListAiConversationsRequest, ListAiConversationsResponse, ListAiPendingActionsRequest, ListAiPendingActionsResponse, ListDraftsResponse, ListNotificationsRequest, ListNotificationsResponse, ListPackagesRequest, ListPackagesResponse, MarkAllNotificationsReadRequest, MarkAllNotificationsReadResponse, MarkNotificationsReadRequest, MarkNotificationsReadResponse, MetadataConflictError, Notification, NotificationPreferences, PublishMetaItemRequest, PublishMetaItemResponse, PublishPackageDraftsResponse, RealtimeConnectRequest, RealtimeConnectResponse, RealtimeDisconnectRequest, RealtimeDisconnectResponse, RealtimeSubscribeRequest, RealtimeSubscribeResponse, RealtimeUnsubscribeRequest, RealtimeUnsubscribeResponse, RegisterDeviceRequest, RegisterDeviceResponse, RejectAiPendingActionResponse, RollbackMetaItemResponse, RuntimeAuthoringIssue, SaveMetaItemRequest, SaveMetaItemResponse, SearchAllHit, SearchAllPageHit, SearchAllResponse, SetPresenceRequest, SetPresenceResponse, UninstallPackageRequest, UninstallPackageResponse, UnregisterDeviceRequest, UnregisterDeviceResponse, UpdateAiConversationRequest, UpdateDataRequest, UpdateDataResponse, UpdateManyDataRequest, UpdateManyDataResponse, UpdateNotificationPreferencesRequest, UpdateNotificationPreferencesResponse, ValidateDataIssue, ValidateDataRequest, ValidateDataResponse } from '@objectstack/spec/api';

// Validate data
const result = AiAgentCapabilitiesSchema.parse(data);

AiAgentCapabilities

Properties

PropertyTypeRequiredDescription
authoringboolean✅Authors app metadata (objects/views/flows)
canvasboolean✅Drives the Live Canvas split view (ADR-0037)
debugboolean✅Exposes the build-doctor debug drawer
resumeboolean✅Turns resume durable multi-step runs (ADR-0013)

AiAgentChatRequest

Properties

PropertyTypeRequiredDescription
messages({ role: Enum<'system' | 'user' | 'assistant' | 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]✅Conversation messages (at least one)
contextRecord<string, any>optionalAgent context (app, object, record, …)
optionsRecord<string, any>optionalRequest options (model, temperature, …)

Nested Shape: AiAgentChatRequest.messages[number]

PropertyTypeRequiredDescription
roleEnum<'system' | 'user' | 'assistant' | 'tool'>✅Message role
contentanyoptionalMessage content: a string, or an array of content parts
partsany[]optionalVercel AI SDK v6 message parts (alternative to content)

AiAgentSummary

Properties

PropertyTypeRequiredDescription
namestring✅Agent name — the :agentName path segment
labelstring✅Display label
rolestring✅Agent role
capabilities{ authoring: boolean; canvas: boolean; debug: boolean; resume: boolean }✅Capability set implied by the agent surface

Nested Shape: AiAgentSummary.capabilities

PropertyTypeRequiredDescription
authoringboolean✅Authors app metadata (objects/views/flows)
canvasboolean✅Drives the Live Canvas split view (ADR-0037)
debugboolean✅Exposes the build-doctor debug drawer
resumeboolean✅Turns resume durable multi-step runs (ADR-0013)

AiAgentsResponse

Properties

PropertyTypeRequiredDescription
agents{ name: string; label: string; role: string; capabilities: object }[]✅Agents this caller may chat with

Nested Shape: AiAgentsResponse.agents[number]

PropertyTypeRequiredDescription
namestring✅Agent name — the :agentName path segment
labelstring✅Display label
rolestring✅Agent role
capabilities{ authoring: boolean; canvas: boolean; debug: boolean; resume: boolean }✅Capability set implied by the agent surface

AiChatRequest

Properties

PropertyTypeRequiredDescription
messages({ role: Enum<'system' | 'user' | 'assistant' | 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]✅Conversation messages (at least one)
systemstringoptionalSystem prompt, prepended as a system message
modelstringoptionalModel id override
temperaturenumberoptionalSampling temperature
maxTokensintegeroptionalMaximum tokens to generate
streambooleanoptionalfalse → JSON response; otherwise the UI Message Stream Protocol
conversationIdstringoptionalConversation to persist this turn into (auto-created when omitted)
turnIdstringoptionalStable per-turn idempotency key (ADR-0013 D1)
optionsRecord<string, any>optionalLegacy nested request options

Nested Shape: AiChatRequest.messages[number]

PropertyTypeRequiredDescription
roleEnum<'system' | 'user' | 'assistant' | 'tool'>✅Message role
contentanyoptionalMessage content: a string, or an array of content parts
partsany[]optionalVercel AI SDK v6 message parts (alternative to content)

AiChatResponse

Properties

PropertyTypeRequiredDescription
contentstring✅Generated text
modelstringoptionalModel that produced it
toolCallsany[]optionalTool calls the model requested (Vercel ToolCallPart)
usage{ promptTokens: number; completionTokens: number; totalTokens: number }optionalToken usage
conversationIdstringoptionalConversation the turn was persisted into

Nested Shape: AiChatResponse.usage

PropertyTypeRequiredDescription
promptTokensnumber✅Tokens consumed by the prompt
completionTokensnumber✅Tokens generated
totalTokensnumber✅prompt + completion

AiCompleteRequest

Properties

PropertyTypeRequiredDescription
promptstring✅Prompt text
optionsRecord<string, any>optionalRequest options (model, temperature, maxTokens, …)

AiConversation

Properties

PropertyTypeRequiredDescription
idstring✅Conversation id
titlestringoptionalTitle / summary
agentIdstringoptionalAgent this conversation is bound to
userIdstringoptionalOwning user
messages({ role: Enum<'system' | 'user' | 'assistant' | 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]✅Message history
createdAtstring✅Creation timestamp (ISO 8601)
updatedAtstring✅Last update timestamp (ISO 8601)
metadataRecord<string, any>optionalConversation metadata

Nested Shape: AiConversation.messages[number]

PropertyTypeRequiredDescription
roleEnum<'system' | 'user' | 'assistant' | 'tool'>✅Message role
contentanyoptionalMessage content: a string, or an array of content parts
partsany[]optionalVercel AI SDK v6 message parts (alternative to content)

AiMessage

Properties

PropertyTypeRequiredDescription
roleEnum<'system' | 'user' | 'assistant' | 'tool'>✅Message role
contentanyoptionalMessage content: a string, or an array of content parts
partsany[]optionalVercel AI SDK v6 message parts (alternative to content)

AiModelsResponse

Properties

PropertyTypeRequiredDescription
models(string | { id: string; label: string; default: boolean })[]✅Models this environment offers
defaultModelstringoptionalDefault model id, when the service reports one

Nested Shape: AiModelsResponse.models[number]

PropertyTypeRequiredDescription
idstring✅Model id
labelstring✅Display label for the picker
defaultboolean✅Whether this is the environment default

AiPendingAction

Properties

PropertyTypeRequiredDescription
idstring✅Pending action id
object_namestring✅Object the action targets
action_namestring✅Action name
tool_namestring✅Tool that would execute it
tool_inputstring✅Serialized tool input
statusEnum<'pending' | 'approved' | 'executed' | 'failed' | 'rejected'>✅Lifecycle status
resultstringoptionalSerialized result, once executed
errorstringoptionalFailure message, when status is failed
rejection_reasonstringoptionalReason given at rejection
conversation_idstringoptionalConversation that proposed it
message_idstringoptionalMessage that proposed it
proposed_bystringoptionalActor that proposed it
decided_bystringoptionalActor that approved or rejected it
proposed_atstring✅Proposal timestamp (ISO 8601)
decided_atstringoptionalDecision timestamp (ISO 8601)

AiPendingActionStatus

Allowed Values

  • pending
  • approved
  • executed
  • failed
  • rejected

AiStreamChunk

Properties

PropertyTypeRequiredDescription
typestring✅Frame type (text-delta, tool-input-available, finish, error, …)

ApproveAiPendingActionResponse

Properties

PropertyTypeRequiredDescription
statusEnum<'executed' | 'failed'>✅Outcome of executing the approved action
resultanyoptionalTool result, when executed
errorstringoptionalFailure reason, when failed

AuditMetaItemRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name
limitnumberoptionalMaximum events to return, newest first. The implementation clamps to [1, 500] and defaults to 100 — out-of-range values are clamped, never refused.

AuditMetaItemResponse

Properties

PropertyTypeRequiredDescription
events{ id: any; occurredAt: string; actor: string; source: string | null; … }[]✅Recent protection-audit events for the item, newest first. See the schema-level note for what an empty array means — and what it never means.

Nested Shape: AuditMetaItemResponse.events[number]

PropertyTypeRequiredDescription
idany✅Row id of the audit event. Opaque to callers.
occurredAtstring✅When the attempt happened (ISO-8601 string).
actorstring✅Who attempted the operation. system when the row recorded no actor.
sourcestring | null✅Which code path recorded the event (e.g. protocol.deleteMetaItem). null when the row recorded none.
operationEnum<'save' | 'publish' | 'rollback' | 'delete' | 'reset'>✅Which metadata-protection door was attempted.
outcomeEnum<'allowed' | 'denied' | 'forced'>✅Whether the attempt went through, was refused, or overrode a lock (ADR-0010 §3.6).
codestring✅Machine-readable verdict code for the outcome (e.g. item_locked). Empty string when the row recorded none.
lockStateEnum<'none' | 'no-overlay' | 'no-delete' | 'full'> | null✅The lock verdict in force at the time of the attempt (ADR-0010 §3.3). null when no lock applied.
lockOverriddenboolean✅True when the attempt went through by overriding a lock (outcome: "forced" rows).
requestIdstring | null✅Correlation id of the originating request. null when the row recorded none.
notestring | null✅Free-text note recorded with the event. null when the row recorded none.

AutomationActionsResponse

Properties

PropertyTypeRequiredDescription
actions{ type: string; version: string; name: string; description?: string; … }[]✅Registered action descriptors (built-in + plugin)
totalinteger✅Number of descriptors returned (after any filters)

Nested Shape: AutomationActionsResponse.actions[number]

Canonical cross-paradigm action/node descriptor (ADR-0018)

PropertyTypeRequiredDescription
typestring✅Registry action/node type (matches the executor type)
versionstring✅Executor version (semver)
namestring✅Display label (or i18n key)
descriptionstringoptionalAction description
iconstringoptionalIcon id resolved by the designer
categoryEnum<'logic' | 'data' | 'io' | 'human' | 'control' | 'custom'>optional (default: "custom")Palette category
paradigmsEnum<'flow' | 'approval'>[]optional (default: ["flow"])Authoring surfaces that may offer this action
configSchemaanyoptionalJSON Schema for the node config (drives the designer form; undeclared keys are rejected at registration)
supportsPausebooleanoptional (default: false)Supports async pause/resume
supportsCancellationbooleanoptional (default: false)Supports cancellation
supportsRetrybooleanoptional (default: true)Supports retry on failure
needsOutboxbooleanoptional (default: false)Dispatch via service-messaging outbox (retry/idempotency/dead-letter)
isAsyncneveroptional[REMOVED] ActionDescriptor.isAsync was removed in @objectstack/spec 17 (ADR-0049) — no execution path ever read it, so declaring it never made a node suspend and omitting it never stopped one. Delete the key. The live mechanism is two-part: an executor suspends by RETURNING suspend: true from execute(), and its descriptor must declare supportsPause: true (plus the resumeAuthority its pauses need) or the engine refuses that suspension. Declaring isAsync: true alongside supportsPause: true was always redundant; declaring it alone was always inert.
handlerContractEnum<'none' | 'pure'>optional (default: "none")Effect contract for author-supplied code this action invokes: 'none' (invokes none) or 'pure' (must not write — it returns a value and the flow graph persists it)
resumeAuthorityEnum<'any' | 'service'>optionalWho may resume a run this node suspended: 'any' (the generic resume route) or 'service' (only the owning service, e.g. approvals). Carries no schema default so an omission stays observable — and an omission is fail-CLOSED at run time, equivalent to 'service': a pausing node whose pause is open to the generic route must declare 'any' explicitly
maturityEnum<'ga' | 'beta' | 'reserved'>optional (default: "ga")Runtime maturity: ga (shipped), beta, or reserved (contract only — designers grey this out)
sourceEnum<'builtin' | 'plugin'>optional (default: "plugin")builtin = platform baseline; plugin = third-party contributed
deprecatedbooleanoptional (default: false)Deprecated alias kept for back-compat
aliasOfstringoptionalCanonical type this alias forwards to

AutomationTriggerRequest

Properties

PropertyTypeRequiredDescription
triggerstring✅
payloadRecord<string, any>✅

AutomationTriggerResponse

Properties

PropertyTypeRequiredDescription
successboolean✅
jobIdstringoptional
resultanyoptional

BatchDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
request{ operation: Enum<'create' | 'update' | 'upsert' | 'delete'>; records: object[]; options?: object }✅Batch operation request

Nested Shape: BatchDataRequest.request

PropertyTypeRequiredDescription
operationEnum<'create' | 'update' | 'upsert' | 'delete'>✅Type of batch operation
records{ id?: string; data?: Record<string, any>; externalId?: string }[]✅Array of records to process (server caps the count — see batch.maxBatchSize)
options{ atomic?: boolean; returnRecords?: boolean; continueOnError?: boolean }optionalBatch operation options

BatchDataResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
operationEnum<'create' | 'update' | 'upsert' | 'delete'>optionalOperation type that was performed
totalnumber✅Total number of records in the batch
succeedednumber✅Number of records that succeeded
failednumber✅Number of records that failed
results{ id?: string; success: boolean; errors?: object[]; data?: Record<string, any>; … }[]✅Detailed results for each record

Nested Shape: BatchDataResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: BatchDataResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: BatchDataResponse.results[number]

PropertyTypeRequiredDescription
idstringoptionalRecord ID if operation succeeded
successboolean✅Whether this record was processed successfully
errors{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }[]optionalArray of errors if operation failed. Branch on errors[0].code — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error. A NON-atomic batch that stopped (the continueOnError: false default) marks its un-attempted tail with the same NOT_ATTEMPTED code — rows before the failure stay written and keep reporting success, since nothing was rolled back.
dataRecord<string, any>optionalFull record data (if returnRecords=true)
indexnumberoptionalIndex of the record in the request array
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: caller-supplied fields LEGALLY stripped from THIS row before it was written — static readonly / TRUE readonlyWhen on update, or the in-engine static readonly strip on create. Per-row because a batch can drop different fields on different rows (readonlyWhen is record-state-dependent). Present ONLY when ≥1 field was dropped for this row; the row still succeeded (success unchanged). A single response header cannot express per-row drops, so this body field is the canonical bulk channel — REST does not emit X-ObjectStack-Dropped-Fields for batches. Optional — omit-when-empty keeps the shape backward-compatible.

CheckPermissionRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name to check permissions for
actionEnum<'create' | 'read' | 'edit' | 'delete' | 'transfer' | 'restore' | 'purge'>✅Action to check
recordIdstringoptionalSpecific record ID (for record-level checks)
fieldstringoptionalSpecific field name (for field-level checks)

CheckPermissionResponse

Properties

PropertyTypeRequiredDescription
allowedboolean✅Whether the action is permitted
reasonstringoptionalReason if denied

CloneDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
idstring✅The ID of the newly created clone.
sourceIdstring✅The ID of the record the clone was copied from.
recordRecord<string, any>✅The created clone, including server-generated fields. Engine-owned values (injected system/audit columns, autonumbers, computed formula/summary fields) are re-derived by the insert path rather than copied from the source; caller-supplied overrides win over copied values.
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: fields that were LEGALLY stripped before the clone was written — a non-system clone cannot seed a static readonly column, whether the value was COPIED from the source row or supplied through overrides (the strip runs inside engine.insert, after the beforeInsert hooks, isSystem-gated, exactly as on createData), so those keys are dropped and the field re-derives its default. Present ONLY when ≥1 field was dropped; the clone still succeeded without them (status/success semantics unchanged). Carried in the 201 body only — this route relays the producer verbatim and sets no X-ObjectStack-Dropped-Fields header. Optional — omit-when-empty keeps the shape backward-compatible for existing clients.
warnings{ field: string; code: string; message: string; rule?: string; … }[]optionalThe advisory validation-rule hits of THIS write: each severity: 'warning' or 'info' rule whose verdict on the written record was "violated", with its rule name, severity, field, code and author-written message — the same element the validate-only preview reports in a row's warnings. Advisory rules never block, so the write succeeded; this is the advisory to show the person who made it. Present ONLY when at least one advisory rule hit; a write with no hit omits the key. Hits of a nested write a hook or a flow made inside this one are not included, and a rule that could not be evaluated is not a hit.

Nested Shape: CloneDataResponse.droppedFields[number]

A write-path strip event: caller-supplied fields legally dropped from the payload

PropertyTypeRequiredDescription
objectstring✅Object the write targeted (resolved object name)
fieldsstring[]✅Caller-supplied field names the engine removed from the write payload
reasonEnum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'>✅Why the fields were dropped: static readonly, a TRUE readonlyWhen predicate, the primary-key strip of a payload id the engine ruled is not an identifier, or a computed (formula) field no driver has a column for

Nested Shape: CloneDataResponse.warnings[number]

PropertyTypeRequiredDescription
fieldstring✅The field the finding is about (_record for an object-level rule).
codestring✅Machine-readable finding code, e.g. required, invalid_type, rule_violation.
messagestring✅Human-readable message — a validation rule's author-written text where one exists.
rulestringoptionalThe name of the ADVISORY validation rule this finding reports. Present only on an advisory rule hit.
severityEnum<'warning' | 'info'>optionalThe advisory rule's declared severity, warning or info. Present only on an advisory rule hit.

CreateAiConversationRequest

Properties

PropertyTypeRequiredDescription
titlestringoptionalInitial title
agentIdstringoptionalAgent to bind the conversation to
metadataRecord<string, any>optionalConversation metadata

CreateDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
dataRecord<string, any>✅The dictionary of field values to insert.

CreateDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
idstring✅The ID of the newly created record.
recordRecord<string, any>✅The created record, including server-generated fields (created_at, owner).
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: caller-supplied fields that were LEGALLY stripped before the record was written — a non-system create cannot seed a static readonly column (the strip runs inside engine.insert, after the beforeInsert hooks, isSystem-gated), so those keys are dropped and the field re-derives its default. Present ONLY when ≥1 field was dropped; the create still succeeded without them (status/success semantics unchanged). REST additionally surfaces this as the X-ObjectStack-Dropped-Fields response header. Optional — omit-when-empty keeps the shape backward-compatible for existing clients.
warnings{ field: string; code: string; message: string; rule?: string; … }[]optionalThe advisory validation-rule hits of THIS write: each severity: 'warning' or 'info' rule whose verdict on the written record was "violated", with its rule name, severity, field, code and author-written message — the same element the validate-only preview reports in a row's warnings. Advisory rules never block, so the write succeeded; this is the advisory to show the person who made it. Present ONLY when at least one advisory rule hit; a write with no hit omits the key. Hits of a nested write a hook or a flow made inside this one are not included, and a rule that could not be evaluated is not a hit.

Nested Shape: CreateDataResponse.droppedFields[number]

A write-path strip event: caller-supplied fields legally dropped from the payload

PropertyTypeRequiredDescription
objectstring✅Object the write targeted (resolved object name)
fieldsstring[]✅Caller-supplied field names the engine removed from the write payload
reasonEnum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'>✅Why the fields were dropped: static readonly, a TRUE readonlyWhen predicate, the primary-key strip of a payload id the engine ruled is not an identifier, or a computed (formula) field no driver has a column for

Nested Shape: CreateDataResponse.warnings[number]

PropertyTypeRequiredDescription
fieldstring✅The field the finding is about (_record for an object-level rule).
codestring✅Machine-readable finding code, e.g. required, invalid_type, rule_violation.
messagestring✅Human-readable message — a validation rule's author-written text where one exists.
rulestringoptionalThe name of the ADVISORY validation rule this finding reports. Present only on an advisory rule hit.
severityEnum<'warning' | 'info'>optionalThe advisory rule's declared severity, warning or info. Present only on an advisory rule hit.

CreateManyDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
recordsRecord<string, any>[]✅Array of records to create

CreateManyDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
recordsRecord<string, any>[]✅Created records
countnumber✅Number of records created
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: caller-supplied readonly fields the in-engine create-side strip (engine.insert, isSystem-gated) removed before the rows were written. AGGREGATED across the batch (one event per object/reason with the UNION of dropped field names) rather than per-row, because this response is { object, records, count } and has no per-row slot to hang a drop set on — a union is the only view it can represent. So read a name here as "at least one row dropped this field", NOT "every row dropped the same set": the strip runs INSIDE engine.insert after the beforeInsert hooks and exempts keys a hook itself wrote, tracked per row: rows where a hook stamped a protected key drop a different set from rows where it did not. Present ONLY when ≥1 field was dropped; the creates still succeeded without them (count/success unchanged). Optional — omit-when-empty keeps the shape backward-compatible. (The same reading applies to the partial-success bulk create, whose batch-level droppedFields names no row for the same reason. The paths that DO carry per-row droppedFields on each result are the bulk UPDATE and the mixed batch, where each row is its own engine call — see BatchOperationResultSchema.)

Nested Shape: CreateManyDataResponse.droppedFields[number]

A write-path strip event: caller-supplied fields legally dropped from the payload

PropertyTypeRequiredDescription
objectstring✅Object the write targeted (resolved object name)
fieldsstring[]✅Caller-supplied field names the engine removed from the write payload
reasonEnum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'>✅Why the fields were dropped: static readonly, a TRUE readonlyWhen predicate, the primary-key strip of a payload id the engine ruled is not an identifier, or a computed (formula) field no driver has a column for

DeleteDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
idstring✅Record ID to delete
expectedVersionstringoptionalOptimistic concurrency token (typically the updated_at value the client read). When provided, the server compares it against the current record version and returns 409 CONCURRENT_UPDATE if they differ. Optional — omit to skip the check. The quoted-empty entity-tag ("") is refused 400 VALIDATION_FAILED, not treated as omitted.

DeleteDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
idstring✅Deleted record ID
successboolean✅Whether deletion succeeded

DeleteManyDataRequest

Properties

PropertyTypeRequiredDescription
idsstring[]✅Array of record IDs to delete (server caps the count — see batch.maxBatchSize)
options{ atomic?: boolean; returnRecords?: boolean; continueOnError?: boolean }optionalDelete options
objectstring✅Object name

Nested Shape: DeleteManyDataRequest.options

PropertyTypeRequiredDescription
atomicbooleanoptional (default: false)Opt-in all-or-nothing. When explicitly true the whole batch runs inside ONE engine transaction: the first failure rolls back every prior write, and the response reports zero successes — each row carries errors[0].code ROLLED_BACK (written, then undone), the causal row its own error, and rows never reached NOT_ATTEMPTED. A runtime that cannot roll back REFUSES the request (501 NOT_IMPLEMENTED) rather than silently degrading to best-effort — probe capabilities.transactionalBatch on /discovery first. Takes precedence over continueOnError. Default false: sequential best-effort.
returnRecordsbooleanoptional (default: false)If true, return full record data in response
continueOnErrorbooleanoptional (default: false)If true (and atomic=false), continue processing remaining records after errors. Default false: the first failure ENDS the run — records before it stay written (nothing is rolled back on this arm), and every record after it is reported errors[0].code NOT_ATTEMPTED rather than omitted, so results always covers all total records and succeeded + failed === total.
validateOnlyneveroptional[REMOVED] options.validateOnly was removed from BatchOptions in @objectstack/spec 17.0.0. It was never implemented: the batch surfaces persisted regardless, so a "dry-run" would have silently executed. There is no dry-run today — drop the key. If you need to preview a batch without writing, open an issue so it can be designed (no-commit cascade / constraint semantics) and reintroduced as a flag that actually holds.

DeleteManyDataResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
operationEnum<'create' | 'update' | 'upsert' | 'delete'>optionalOperation type that was performed
totalnumber✅Total number of records in the batch
succeedednumber✅Number of records that succeeded
failednumber✅Number of records that failed
results{ id?: string; success: boolean; errors?: object[]; data?: Record<string, any>; … }[]✅Detailed results for each record

Nested Shape: DeleteManyDataResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: DeleteManyDataResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: DeleteManyDataResponse.results[number]

PropertyTypeRequiredDescription
idstringoptionalRecord ID if operation succeeded
successboolean✅Whether this record was processed successfully
errors{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }[]optionalArray of errors if operation failed. Branch on errors[0].code — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error. A NON-atomic batch that stopped (the continueOnError: false default) marks its un-attempted tail with the same NOT_ATTEMPTED code — rows before the failure stay written and keep reporting success, since nothing was rolled back.
dataRecord<string, any>optionalFull record data (if returnRecords=true)
indexnumberoptionalIndex of the record in the request array
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: caller-supplied fields LEGALLY stripped from THIS row before it was written — static readonly / TRUE readonlyWhen on update, or the in-engine static readonly strip on create. Per-row because a batch can drop different fields on different rows (readonlyWhen is record-state-dependent). Present ONLY when ≥1 field was dropped for this row; the row still succeeded (success unchanged). A single response header cannot express per-row drops, so this body field is the canonical bulk channel — REST does not emit X-ObjectStack-Dropped-Fields for batches. Optional — omit-when-empty keeps the shape backward-compatible.

DeleteMetaItemRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name
parentVersionstringoptionalADR-0008 optimistic-concurrency pin: the version token the caller believes is current (on the REST door, the If-Match request header). Present, a concurrent edit is reported as a 409 conflict instead of silently reset; absent = last-write-wins against the current row (Studio's "Reset" button is unpinned).
actorstringoptionalIdentity recorded on the delete's history tombstone row. On the REST door this is the request's authenticated identity (one producer) — never a caller-supplied header. Absent, the event is recorded actor-less (null), deliberately not attributed to "system".
stateEnum<'active' | 'draft'>optionalWhich lifecycle row to discard: draft discards the pending draft overlay only (the still-active overlay, if any, keeps serving); active or absent resets the live row. Absent defaults to active.
dropStoragebooleanoptionalDestructive opt-in, default false: also drop the object's physical table after the metadata row is removed (object type + active state only; never sys_ tables). Used by the "discard a previewed object" flow so a publish-to-preview leaves no orphan table.
memberstringoptionalBundle member selector, for a metadata type whose identity is a pair (today only email_template, identified by (name, locale)): the member this request addresses, spelled as its discriminator value (zh-CN). Absent, the canonical member (en-US for email_template), which is what a bare-name read answers; naming the canonical value is the same request. It is not the response translation locale, which ?locale= and Accept-Language choose. Refused with 400 VALIDATION_ERROR on a type that has no members, and for a blank value or one longer than a stored member can be (100 characters). A well-formed member with nothing stored or shipped answers as that door answers an absent item.
packageIdstring | nulloptionalADR-0048 — the software package whose row this delete removes (sys_metadata.package_id; ?package=<id> on the REST door, where all and an empty value name no package), as a save names the row it writes. Named, the delete removes that package's row and no other; a package that holds no row removes nothing. Absent or null names no package: the delete of the active row then removes the row of the one package the item's active rows are bound to, is refused 409 RESOURCE_CONFLICT when they are bound to two or more, and with none removes the env-local row (no package stamp); the discard of the draft removes the draft a draft save naming no package writes, the one the per-item publish naming none promotes, and is refused where that publish is (see PublishMetaItemRequestSchema.packageId). That row is the one parentVersion is judged against. When another row of the item is still stored afterwards, the receipt message names it.

DeleteMetaItemResponse

Properties

PropertyTypeRequiredDescription
successboolean✅
resetbooleanoptional
seqintegeroptionalMonotonic sequence number of the metadata event this delete appended to the item history (sys_metadata_history.event_seq) — the ordering token the history/audit trail is read by, and the same key both write-verb siblings declare. Unlike version it is not an OCC token. Optional HERE, unlike on those siblings, because only the repository path's delete-ful branch appends an event: a no-op reset (no overlay row existed) and the control-plane legacy raw-engine path both answer without one. Absence means "this branch appended no history event", never "nothing was deleted" — read reset for that.
projectionApplied{ success: boolean; error?: string }optionalOutcome of the awaited ADR-0094 mutation projector — the post-persist step that materializes this metadata into its derived data-plane read model. The same receipt {@link SaveMetaItemResponseSchema} and {@link PublishMetaItemResponseSchema} carry, because the projector runs on all three verbs of this door: on a delete it re-reads the layered state and either retires the derived record or resets it to the artifact baseline. Present ONLY when a projector is registered for this metadata type AND a row was actually deleted, which is why it is optional: its absence means "no projector ran", never "the projection failed". Best-effort by design — a projector failure is reported here and logged, never thrown, so a caller that needs the read model to be live must check projectionApplied.success rather than rely on the 200.
messagestringoptional

Nested Shape: DeleteMetaItemResponse.projectionApplied

PropertyTypeRequiredDescription
successboolean✅False when the projector threw; the metadata delete itself still succeeded.
errorstringoptionalProjector failure message, present only when success is false.

DiffMetaItemResponse

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type of the diffed item.
namestring✅Name of the diffed item.
fromVersionnumber | null✅The older side's history version, null when that side is absent (e.g. the item had no earlier version).
toVersionnumber | null✅The newer side's history version, null when that side is absent.
added{ path: string; value: any }[]✅Members present in to and absent in from.
removed{ path: string; value: any }[]✅Members present in from and absent in to.
changed{ path: string; from: any; to: any }[]✅Members present on both sides with different values.

Nested Shape: DiffMetaItemResponse.added[number]

PropertyTypeRequiredDescription
pathstring✅Dot path of the added member.
valueany✅The added value.

Nested Shape: DiffMetaItemResponse.removed[number]

PropertyTypeRequiredDescription
pathstring✅Dot path of the removed member.
valueany✅The removed value.

Nested Shape: DiffMetaItemResponse.changed[number]

PropertyTypeRequiredDescription
pathstring✅Dot path of the changed member.
fromany✅The older side's value.
toany✅The newer side's value.

DisablePackageRequest

Disable package request

Properties

PropertyTypeRequiredDescription
idstring✅Package ID to disable

DisablePackageResponse

Disable package response

Properties

PropertyTypeRequiredDescription
package{ manifest: object; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; installedAt?: string; … }✅Disabled package details
messagestringoptionalDisable status message

Nested Shape: DisablePackageResponse.package

PropertyTypeRequiredDescription
manifest{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }✅Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema
statusEnum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>optional (default: "installed")Package state: installed, disabled, installing, upgrading, uninstalling, or error
enabledbooleanoptional (default: true)Whether the package is currently enabled
installedAtstringoptionalInstallation timestamp
updatedAtstringoptionalLast update timestamp
installedVersionstringoptionalCurrently installed version for quick access
previousVersionstringoptionalVersion before the last upgrade
statusChangedAtstringoptionalStatus change timestamp
errorMessagestringoptionalError message when status is error
settingsRecord<string, any>optionalUser-provided configuration settings
upgradeHistory{ fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[]optionalVersion upgrade history
registeredNamespacesstring[]optionalNamespace prefixes registered by this package

EnablePackageRequest

Enable package request

Properties

PropertyTypeRequiredDescription
idstring✅Package ID to enable

EnablePackageResponse

Enable package response

Properties

PropertyTypeRequiredDescription
package{ manifest: object; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; installedAt?: string; … }✅Enabled package details
messagestringoptionalEnable status message

Nested Shape: EnablePackageResponse.package

PropertyTypeRequiredDescription
manifest{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }✅Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema
statusEnum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>optional (default: "installed")Package state: installed, disabled, installing, upgrading, uninstalling, or error
enabledbooleanoptional (default: true)Whether the package is currently enabled
installedAtstringoptionalInstallation timestamp
updatedAtstringoptionalLast update timestamp
installedVersionstringoptionalCurrently installed version for quick access
previousVersionstringoptionalVersion before the last upgrade
statusChangedAtstringoptionalStatus change timestamp
errorMessagestringoptionalError message when status is error
settingsRecord<string, any>optionalUser-provided configuration settings
upgradeHistory{ fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[]optionalVersion upgrade history
registeredNamespacesstring[]optionalNamespace prefixes registered by this package

FindDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅The unique machine name of the object to query (e.g. "account").
query{ object: string; fields?: string[]; where?: [string, string, any] | [string, string] | [string, object, ...object[]] | object[] | Record<string, any> | any; search?: string | object; … }optionalStructured query definition (filter, sort, select, pagination) — the canonical QueryAST or its transport spelling.

Nested Shape: FindDataRequest.query

PropertyTypeRequiredDescription
objectstring✅Object name (e.g. account)
fieldsstring[]optionalFields to retrieve — names of the queried object's OWN columns. A dotted path (owner.name) is not a projection: no driver resolves one, and the ingress refuses it with 400 INVALID_FIELD. Related data is read with expand, whose nested QueryAST both filters (where) and selects (fields) the related record's columns. The projection must RETAIN the foreign-key column: fields: ['title'] with expand: 'project_id' resolves nothing, because the relation is carried by that key — add 'project_id' and it works. Where the value is wanted on the queried object itself, denormalise it onto that object (a stored field, written when the source changes), the same remedy the sort axis prescribes.
where[string, string, any] | [string, string] | [string, object, ...object[]] | object[] | Record<string, any> | anyoptionalFiltering criteria (WHERE) — a filter condition, or the input-only FilterArray sugar (['status', '=', 'open']), which is lowered through parseFilterAST before the query is produced.
searchstring | { query: string; fields?: string[]; fuzzy?: boolean; operator?: Enum<'and' | 'or'>; … }optionalFull-text search — the query text (canonical, ADR-0061 D1), or a structured FullTextSearch configuration
searchFieldsstring[]optionalNarrow the search to these fields (server-intersected with the allowed searchable set — can only narrow, never widen; ADR-0061 D1)
orderBy{ field: string; order?: Enum<'asc' | 'desc'> }[]optionalSorting instructions (ORDER BY)
limitnumberoptionalMax records to return (LIMIT)
offsetnumberoptionalRecords to skip (OFFSET)
topnumberoptionalAlias for limit (OData compatibility)
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
joinsneveroptional[REMOVED] query.joins was removed in @objectstack/spec 17 (ADR-0049) — no engine or driver ever read it: a query carrying joins behaved exactly as if the key were absent, while its name squatted on the reserved REST parameter set. Delete the key. Related records are read through expand — expand: { owner_id: { object: 'user', fields: ['name'] } } — which the engine resolves via batch $in queries, and whose nested query selects the related record's own columns. Keep the foreign key in your own projection (fields: ['title', 'owner_id']): the relation is carried by that column, so projecting it away leaves expansion nothing to resolve. A dotted fields path is NOT a replacement — no driver ever resolved one and the ingress refuses it (400 INVALID_FIELD).
aggregations{ function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>; field?: string; alias: string; filter?: any }[]optionalAggregation functions
groupBy(string | { field: string; dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; alias?: string })[]optionalGROUP BY targets (strings or {field, dateGranularity?} objects for date bucketing)
havinganyoptionalHAVING — filter over the AGGREGATED rows (aggregation aliases + groupBy projections); applied engine-side after aggregation
windowFunctionsneveroptional[REMOVED] query.windowFunctions was removed in @objectstack/spec 17 (ADR-0049) — find() never applied it: no engine or driver read the key on the query path, so every OVER clause it declared was silently dropped. Delete the key. Window functions are a SQL-driver capability behind SqlDriver.findWithWindowFunctions(object, query) (embedder-level; not on the IDataDriver contract or the REST surface); request-level analytics are aggregations + groupBy.
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optionalRecursive relation loading map. Keys are lookup/master_detail field names; values are nested QueryAST objects that control select (fields) and filter (where, AND-merged with the batch $in), plus further expansion on the related object. The engine resolves expand via batch $in queries (driver-agnostic) with a default max depth of 3; per-parent limit/offset/orderBy are NOT applied on this path.
$filter[string, string, any] | [string, string] | [string, object, ...object[]] | object[] | Record<string, any> | any | nulloptionalTransport spelling of where (OData $filter)
filters[string, string, any] | [string, string] | [string, object, ...object[]] | object[] | Record<string, any> | any | nulloptionalTransport spelling of where (plural of filter)
$topnumber | string | nulloptionalTransport spelling of limit (OData $top) — a number, or the digits a querystring carries it as
$skipnumber | string | nulloptionalTransport spelling of offset (OData $skip) — a number, or the digits a querystring carries it as
$orderbyRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order?: Enum<'asc' | 'desc'> }[] | nulloptionalTransport spelling of orderBy (OData $orderby)
$selectstring | string[] | nulloptionalTransport spelling of fields (OData $select)
$expandstring | string[] | Record<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }> | nulloptionalTransport spelling of expand (OData $expand)
$searchstring | { query: string; fields?: string[]; fuzzy?: boolean; operator?: Enum<'and' | 'or'>; … } | nulloptionalTransport spelling of search (OData $search)
$searchFieldsstring | string[] | nulloptionalTransport spelling of searchFields (OData $searchFields)
$countboolean | Enum<'true' | 'false'> | nulloptionalTransport spelling of the response total-count flag (OData $count)
countboolean | Enum<'true' | 'false'> | nulloptionalResponse total-count flag — only an explicit false skips the COUNT query
filter[string, string, any] | [string, string] | [string, object, ...object[]] | object[] | Record<string, any> | any | nulloptionalTransport spelling of where
selectstring | string[] | nulloptionalTransport spelling of fields
sortRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order?: Enum<'asc' | 'desc'> }[] | nulloptionalTransport spelling of orderBy
skipnumber | string | nulloptionalTransport spelling of offset
populatestring | string[] | nulloptionalTransport spelling of expand

FindDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅The object name for the returned records.
recordsRecord<string, any>[]✅The list of matching records.
totalnumberoptionalTotal number of records matching the filter (if requested).
nextCursorstringoptionalCursor for the next page of results (cursor-based pagination).
hasMorebooleanoptionalTrue if there are more records available (pagination).

FindReferencesToMetaResponse

Properties

PropertyTypeRequiredDescription
references{ type: string; name: string; label?: string; path: string; … }[]✅Every found reference to the addressed item.

Nested Shape: FindReferencesToMetaResponse.references[number]

PropertyTypeRequiredDescription
typestring✅Metadata type of the REFERRING item.
namestring✅Name of the referring item.
labelstringoptionalDisplay label of the referring item, when it has one.
pathstring✅Where inside the referring item the reference sits (dot path).
kindstring✅What kind of reference this is (e.g. which key carries it).

FlowRegistration

Union Options

This schema accepts one of the following structures:

Option 1

Properties

PropertyTypeRequiredDescription
state'registered'✅The engine holds a registration under the flow's name.

Option 2

Properties

PropertyTypeRequiredDescription
state'refused'✅The engine refused to register the stored flow and holds nothing under its name: it never runs.
reason{ code?: string; message: string }✅What the engine threw, verbatim.

Nested Shape: FlowRegistration[state='refused'].reason

PropertyTypeRequiredDescription
codestringoptionalThe machine code the engine's refusal carried, verbatim. Absent when the thrown error carried none.
messagestring✅The engine's refusal message, verbatim. Render it as sent; do not parse it.

Option 3

Properties

PropertyTypeRequiredDescription
state'unregistered'✅The engine holds nothing under the flow's name and recorded no refusal for it.


FlowRegistrationRefusal

Properties

PropertyTypeRequiredDescription
codestringoptionalThe machine code the engine's refusal carried, verbatim. Absent when the thrown error carried none.
messagestring✅The engine's refusal message, verbatim. Render it as sent; do not parse it.

GetDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
idstring✅The unique record identifier (primary key).
selectstring[]optionalFields to include in the response (allowlisted query param).
expandstring[]optionalLookup/master_detail field names to expand. The engine resolves these via batch $in queries, replacing foreign key IDs with full objects.

GetDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
idstring✅The record ID.
recordRecord<string, any>✅The complete record data.

GetDiscoveryRequest

Properties

PropertyTypeRequiredDescription

GetDiscoveryResponse

Properties

PropertyTypeRequiredDescription
namestringoptional
versionstring✅
environmentEnum<'production' | 'sandbox' | 'development'>optionalDeployment posture a discovery response advertises. Deliberately three coarse buckets — a client reads this to answer "am I talking to production?", not to identify a specific environment (that is sys_environment / EnvironmentTypeSchema, a richer 7-member taxonomy).
routes{ data: string; metadata: string; discovery?: string; ui?: string; … }optional
locale{ default: string; supported: string[]; timezone: string }optional
servicesRecord<string, { enabled: boolean; status: Enum<'available' | 'registered' | 'unavailable' | 'degraded' | 'stub'>; handlerReady?: boolean; route?: string; … }>optionalPer-service availability map keyed by CoreServiceName
capabilities{ comments: object; automation: object; cron: object; search: object; … }optionalHierarchical capability descriptors — the full WellKnownCapabilities vocabulary, every key present
schemaDiscovery{ openapi?: string; jsonSchema?: string }optionalSchema discovery endpoints for API toolchain integration
scoping{ enabled: boolean; resolution: Enum<'required' | 'optional' | 'auto'>; scoped: boolean; environmentId?: string }optionalEnvironment-scoping posture, added by the REST discovery endpoint
authFamilies{ admin: boolean }optionalWhich optional better-auth route families are mounted under routes.auth — the same source as GET {routes.auth}/config features; absent when no auth service answers
metadataRecord<string, any>optionalCustom metadata key-value pairs for extensibility
apiNamestringoptionalAPI name (deprecated — use name; removed in protocol 18)

Nested Shape: GetDiscoveryResponse.routes

PropertyTypeRequiredDescription
datastring✅e.g. /api/v1/data
metadatastring✅e.g. /api/v1/meta
discoverystringoptionale.g. /api/v1/discovery
uistringoptionale.g. /api/v1/ui
authstringoptionale.g. /api/v1/auth
automationstringoptionale.g. /api/v1/automation
storagestringoptionale.g. /api/v1/storage
analyticsstringoptionale.g. /api/v1/analytics
packagesstringoptionale.g. /api/v1/packages
datasourcesstringoptionale.g. /api/v1/datasources — base for the datasources/:name/external/* federation-admin family; absent when no host mounts it
emailstringoptionale.g. /api/v1/email — base for the email/send endpoint; absent when no host mounts it
approvalsstringoptionale.g. /api/v1/approvals
realtimestringoptionale.g. /api/v1/realtime — present only when a realtime transport is actually mounted
notificationsstringoptionale.g. /api/v1/notifications
aistringoptionale.g. /api/v1/ai
i18nstringoptionale.g. /api/v1/i18n
mcpstringoptionale.g. /api/v1/mcp — always the unscoped base; absent when MCP is disabled or unserveable

Nested Shape: GetDiscoveryResponse.services[string]

PropertyTypeRequiredDescription
enabledboolean✅Whether the slot is filled by something this host delivers. NOT "a channel exists": subscribing requires handlerReady:true AND a connectable route (isSubscribableChannel).
statusEnum<'available' | 'registered' | 'unavailable' | 'degraded' | 'stub'>✅available = fully operational, registered = route declared but handler unverified, unavailable = not installed, degraded = partial, stub = placeholder that returns 501
handlerReadybooleanoptionalWhether the HTTP handler is confirmed to be mounted. Omitted = readiness unknown/unverified; true = handler mounted; false = handler missing or stub (likely 501).
routestringoptionale.g. /api/v1/analytics
providerstringoptionale.g. "objectql", "plugin-redis", "driver-memory"
versionstringoptionalSemantic version of the service implementation (e.g. "3.0.6")
messagestringoptionale.g. "Install plugin-workflow to enable"
rateLimit{ requestsPerMinute?: integer; requestsPerHour?: integer; burstLimit?: integer; retryAfterMs?: integer }optionalRate limit and quota info for this service

Nested Shape: GetDiscoveryResponse.capabilities

PropertyTypeRequiredDescription
comments{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend supports record comments / chatter (the sys_comment object served via the data API)
automation{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend supports Automation CRUD (flows, triggers)
cron{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend supports cron scheduling
search{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend supports full-text search
export{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend supports async export
chunkedUpload{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend supports chunked (multipart) uploads
transactionalBatch{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend exposes the atomic cross-object batch endpoint (POST {basePath}/batch, /ADR-0034): all ops commit or roll back together in one transaction. Lets clients skip non-atomic client-side simulation instead of runtime-probing 404/405/501. True ⟺ the /batch route is mounted AND the runtime can honour a transaction.
websockets{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend mounts a realtime push surface (WebSocket/SSE) clients can subscribe to. Derived from isSubscribableChannel(services.realtime): handlerReady true AND a connectable route.
files{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether a file-storage surface (upload/download/attachments) is served
analytics{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend serves the analytics / BI query surface
ai{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend serves the AI surface (NLQ, chat, agents, suggest)
notifications{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend serves the notification surface (inbox, delivery)
i18n{ enabled: boolean; features?: Record<string, boolean>; description?: string }✅Whether the backend serves the i18n surface (translations, locale negotiation)

Nested Shape: GetDiscoveryResponse.schemaDiscovery

PropertyTypeRequiredDescription
openapistringoptionalURL to OpenAPI (Swagger) specification (e.g., "/api/v1/openapi.json")
jsonSchemastringoptionalURL to JSON Schema definitions

Nested Shape: GetDiscoveryResponse.scoping

PropertyTypeRequiredDescription
enabledboolean✅Whether environment-scoped routes are mounted at all
resolutionEnum<'required' | 'optional' | 'auto'>✅How the environment id is resolved when scoping is enabled (mirrors RestApiConfig.projectResolution)
scopedboolean✅Whether THIS response was served from the environment-scoped mount
environmentIdstringoptionalThe resolved environment id — present only on a scoped mount

Nested Shape: GetDiscoveryResponse.authFamilies

PropertyTypeRequiredDescription
adminboolean✅Whether the better-auth admin family ({routes.auth}/admin/*: list-users, set-role, update-user, ban-user, …) is mounted. Same value as features.admin on GET {routes.auth}/config, read from the same source; false means those routes answer a plain 404 on this deployment.

GetEffectivePermissionsRequest

Properties

PropertyTypeRequiredDescription

GetEffectivePermissionsResponse

Properties

PropertyTypeRequiredDescription
objectsRecord<string, { allowCreate?: boolean; allowRead?: boolean; allowEdit?: boolean; allowDelete?: boolean; … }>✅Effective object permissions keyed by object name
systemPermissionsstring[]✅Effective system-level permissions

Nested Shape: GetEffectivePermissionsResponse.objects[string]

PropertyTypeRequiredDescription
allowCreatebooleanoptional (default: false)Create permission
allowReadbooleanoptional (default: false)Read permission
allowEditbooleanoptional (default: false)Edit permission
allowDeletebooleanoptional (default: false)Delete permission
allowExportbooleanoptionalUser-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords.
allowTransferbooleanoptional (default: false)[RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id)
allowRestoreneveroptional[REMOVED] objects.<object>.allowRestore was removed in @objectstack/spec 17 (ADR-0049) — the restore ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched restore stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
allowPurgeneveroptional[REMOVED] objects.<object>.allowPurge was removed in @objectstack/spec 17 (ADR-0049) — the purge ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched purge stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
viewAllRecordsbooleanoptional (default: false)View All Data (Bypass Sharing)
modifyAllRecordsbooleanoptional (default: false)Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies.
readScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Read depth: own|unit|unit_and_below|org
writeScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Write depth: own|unit|unit_and_below|org
apiOperationsEnum<'get' | 'list' | 'create' | 'update' | 'delete' | 'upsert' | 'bulk' | …>[]optionalServer-resolved effective API operations for this object. Present only when the object tightens exposure via apiMethods; absent = default-allow. The frontend renders this effective set, never the raw whitelist. Vocabulary is the EFFECTIVE ApiOperation set (six primitives + eight derived verbs), not the authored six-value ApiMethod enum.

GetFieldLabelsRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
localestring✅BCP-47 locale code

GetFieldLabelsResponse

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
localestring✅Locale code
labelsRecord<string, { label: string; help?: string; options?: Record<string, string> }>✅Field labels keyed by field name

Nested Shape: GetFieldLabelsResponse.labels[string]

PropertyTypeRequiredDescription
labelstring✅Translated field label
helpstringoptionalTranslated help text
optionsRecord<string, string>optionalTranslated option labels

GetLocalesRequest

Properties

PropertyTypeRequiredDescription

GetLocalesResponse

Properties

PropertyTypeRequiredDescription
locales{ code: string; label: string; isDefault?: boolean }[]✅Available locales

Nested Shape: GetLocalesResponse.locales[number]

PropertyTypeRequiredDescription
codestring✅BCP-47 locale code (e.g., en-US, zh-CN)
labelstring✅Locale label. Equals code on every serving surface today — the client names locales for its UI
isDefaultbooleanoptional (default: false)Whether this is the default locale

GetMetaDiagnosticsResponse

Properties

PropertyTypeRequiredDescription
entries{ type: string; name: string; diagnostics: object }[]✅One entry per item that failed validation (after filters).
totalnumber✅Number of entries in this answer.
scannedTypesnumber✅How many metadata types the sweep visited.
scannedItemsnumber✅How many items the sweep visited.
statsRecord<string, { count: number; locked: number; packages: string[] }>✅Per-type aggregate stats, keyed by metadata type — computed in the same sweep so a directory page renders tile counts and a package filter in one round-trip.

Nested Shape: GetMetaDiagnosticsResponse.entries[number]

PropertyTypeRequiredDescription
typestring✅Metadata type of the failing item.
namestring✅Name of the failing item.
diagnostics{ valid: boolean; errors?: object[]; warnings?: object[] }✅The spec-validation verdict for the item — the same MetadataValidationResult the write path answers.

Nested Shape: GetMetaDiagnosticsResponse.stats[string]

PropertyTypeRequiredDescription
countnumber✅Items of this type present.
lockednumber✅Items of this type currently lock-protected.
packagesstring[]✅Packages contributing items of this type.

GetMetaItemCachedRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name
cacheRequest{ ifNoneMatch?: string; ifModifiedSince?: string; cacheControl?: object }optionalCache validation parameters
localestringoptionalResolved response locale. Folded into the ETag so a language switch never returns a stale-locale 304 — metadata is translated after the cache validator check (issue).

Nested Shape: GetMetaItemCachedRequest.cacheRequest

PropertyTypeRequiredDescription
ifNoneMatchstringoptionalETag value for conditional request (If-None-Match header)
ifModifiedSincestringoptionalTimestamp for conditional request (If-Modified-Since header)
cacheControl{ directives: Enum<'public' | 'private' | 'no-cache' | 'no-store' | 'must-revalidate' | 'max-age'>[]; maxAge?: number; staleWhileRevalidate?: number; staleIfError?: number }optionalClient cache control preferences

GetMetaItemCachedResponse

Properties

PropertyTypeRequiredDescription
dataanyoptionalMetadata payload (omitted for 304 Not Modified)
etag{ value: string; weak?: boolean }optionalETag for this resource version
lastModifiedstringoptionalLast modification timestamp
cacheControl{ directives: Enum<'public' | 'private' | 'no-cache' | 'no-store' | 'must-revalidate' | 'max-age'>[]; maxAge?: number; staleWhileRevalidate?: number; staleIfError?: number }optionalCache control directives
notModifiedbooleanoptional (default: false)True if resource has not been modified (304 response)
versionstringoptionalMetadata version identifier
registration{ state: 'registered' } | { state: 'refused'; reason: object } | { state: 'unregistered' }optionalThe automation engine's verdict on the flow in data, read live on every call and never part of etag (see FlowRegistrationSchema). Present only for a flow read answered by a process that runs an automation service; a read that carries it is never answered notModified. Absent otherwise, and absence means no engine judged it, never registered.

Nested Shape: GetMetaItemCachedResponse.etag

PropertyTypeRequiredDescription
valuestring✅ETag value (hash or version identifier)
weakbooleanoptional (default: false)Whether this is a weak ETag

Nested Shape: GetMetaItemCachedResponse.cacheControl

PropertyTypeRequiredDescription
directivesEnum<'public' | 'private' | 'no-cache' | 'no-store' | 'must-revalidate' | 'max-age'>[]✅Cache control directives
maxAgenumberoptionalMaximum cache age in seconds (unit per HTTP Cache-Control max-age (RFC 9111 §5.2.2.1))
staleWhileRevalidatenumberoptionalAllow serving stale content while revalidating (seconds) (unit per HTTP Cache-Control stale-while-revalidate (RFC 5861 §3))
staleIfErrornumberoptionalAllow serving stale content on error (seconds) (unit per HTTP Cache-Control stale-if-error (RFC 5861 §4))

Nested Shape: GetMetaItemCachedResponse.registration[state='registered']

PropertyTypeRequiredDescription
state'registered'✅The engine holds a registration under the flow's name.

Nested Shape: GetMetaItemCachedResponse.registration[state='refused']

PropertyTypeRequiredDescription
state'refused'✅The engine refused to register the stored flow and holds nothing under its name: it never runs.
reason{ code?: string; message: string }✅What the engine threw, verbatim.

Nested Shape: GetMetaItemCachedResponse.registration[state='unregistered']

PropertyTypeRequiredDescription
state'unregistered'✅The engine holds nothing under the flow's name and recorded no refusal for it.

GetMetaItemLayeredRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name
packageIdstringoptionalOptional package ID — scopes the code layer so a same-name collision resolves to the requested package's artifact (ADR-0048).

GetMetaItemLayeredResponse

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name (canonical singular)
namestring✅Item name
codeany✅LAYER 1 — the packaged artifact baseline exactly as shipped, before any tenant customization. null when no artifact ships this item (it exists only as an overlay).
overlayany✅LAYER 2 — the stored customization row ALONE, not merged with code. null when the environment has not customized the item.
overlayScopeEnum<'env'> | null✅Which scope the overlay row was read from: env, the environment layer — the only layer a read serves (ADR-0131 D6). null exactly when overlay is null.
effectiveany✅LAYER 3 — the merged result, i.e. the value an ordinary GET /meta/:type/:name would return under item. null when the item resolves to nothing at all.
_diagnostics{ valid: boolean; errors?: object[]; warnings?: object[] }optionalLoad-time spec-validation verdict for effective, so the Studio edit page can raise invalid-metadata banners and inline field errors without a second round trip. ABSENT for metadata types that register no Zod schema (function / service / router) — absence means "no opinion", never "valid".
lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>✅Resolved lock verdict (ADR-0010 §3.3), artifact winning over overlay. Always present on this path.
lockReasonstringoptionalHuman-readable explanation shown next to a refused write. Present only when the resolved item declares _lockReason.
lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalWhich layer asserted the lock. Present only when the resolved item declares _lockSource.
lockDocsUrlstringoptionalDocumentation link surfaced beside lockReason. Present only when the resolved item declares _lockDocsUrl.
provenanceEnum<'package' | 'org' | 'env-forced'>optionalWhere the item came from (package | org | env-forced). Present only when the resolved item declares _provenance.
packageIdstringoptionalOwning package machine id. Present only when the resolved item declares _packageId.
packageVersionstringoptionalOwning package version. Present only when the resolved item declares _packageVersion.
editableboolean✅Whether an overlay write is permitted. Always present on this path.
deletableboolean✅Whether deleting the overlay is permitted. Always present on this path.
resettableboolean✅Whether the item can be reset to its packaged default. Always present on this path.

Nested Shape: GetMetaItemLayeredResponse._diagnostics

PropertyTypeRequiredDescription
validboolean✅Whether the metadata is valid
errors{ path: string; message: string; code?: string }[]optionalValidation errors
warnings{ path: string; message: string }[]optionalValidation warnings

GetMetaItemRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name: the item's own name. For type: 'app' only, it may instead be the id of the package that ships the app (manifest.id, reverse-domain, e.g. com.acme.crm): with one app per package the app IS the package container, and the package id is its route key (ADR-0048 §3.1). The two grammars are disjoint (an app name is snake_case with no dot; a package id matches MANIFEST_ID_PATTERN, with at least one dot), so an app key in the package-id grammar always names the package (isAppPackageIdKey). That read answers the package's one app as the by-name read answers it, under the app's own name, and the per-caller read gate judges it as it judges that app read by name (an app withheld from the caller answers 403 PERMISSION_DENIED). A package with no app, or with more than one, answers as an absent item. A packageId naming a different package is refused with 400 INVALID_REQUEST. Every other type, and every other key, is read by name.
packageIdstringoptionalOptional package ID to filter items by
memberstringoptionalBundle member selector, for a metadata type whose identity is a pair (today only email_template, identified by (name, locale)): the member this request addresses, spelled as its discriminator value (zh-CN). Absent, the canonical member (en-US for email_template), which is what a bare-name read answers; naming the canonical value is the same request. It is not the response translation locale, which ?locale= and Accept-Language choose. Refused with 400 VALIDATION_ERROR on a type that has no members, and for a blank value or one longer than a stored member can be (100 characters). A well-formed member with nothing stored or shipped answers as that door answers an absent item.
stateEnum<'active' | 'draft'>optionalDraft-visibility switch — which lifecycle row to read (strict mode): 'draft' opens the pending draft buffer (Studio's editor read) and fails when no draft exists; absent or 'active' reads the live published row. Distinct from previewDrafts, which FALLS BACK to the active row when no draft exists. Declaration ≠ authorization: this member only selects which stored row is read — ADR-0106 masking is unaffected, and draft access is gated upstream, not by this schema.
previewDraftsbooleanoptionalDraft-visibility switch (ADR-0033 draft-overlay preview, non-strict): when true and state is not 'draft', a pending draft row is preferred if one exists, else the read falls back to the active row — the render path degrades to the published value instead of erroring. A served draft is tagged _draft: true so UIs can badge it. Declaration ≠ authorization: this member only switches which row is read, and ADR-0106 masking is unaffected — draft preview is admin-gated upstream, not by this schema.

GetMetaItemResponse

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name
itemany✅Metadata item definition
sortability{ fields: Record<string, object> }optionalPer-column sortability projection — present exactly when type is object, on every serving branch. Computed at serve time from the served document via the spec's own storage predicates; consumers render sort affordances from this signal and never re-derive it from field type. See ObjectSortabilitySchema for the closed category set.
lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalResolved lock verdict for this item (ADR-0010 §3.3). none means unlocked; no-overlay / no-delete / full mean the write doors refuse the corresponding write with 403. Joins two refusals: the document's own _lock (ITEM_LOCKED; the packaged artifact wins over any overlay), and the locked packaged base — an item a code package ships, on a type with no per-org overlay channel (NOT_OVERRIDABLE, or ITEM_LOCKED when the write names the read-only package).
lockReasonstringoptionalHuman-readable explanation shown next to a refused write. Present only when the resolved item declares _lockReason.
lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalWhich layer asserted the lock. Present only when the resolved item declares _lockSource.
lockDocsUrlstringoptionalDocumentation link surfaced beside lockReason. Present only when the resolved item declares _lockDocsUrl.
provenanceEnum<'package' | 'org' | 'env-forced'>optionalWhere the item came from (package | org | env-forced). Present only when the resolved item declares _provenance.
packageIdstringoptionalOwning package machine id. Present only when the resolved item declares _packageId.
packageVersionstringoptionalOwning package version. Present only when the resolved item declares _packageVersion.
editablebooleanoptionalWhether an overlay write is permitted — false iff lock is no-overlay or full. A derived verdict: do not recompute it from lock client-side.
deletablebooleanoptionalWhether deleting the overlay is permitted — false iff lock is no-delete or full.
resettablebooleanoptionalWhether the item can be reset to its packaged default — true iff it is artifact-backed, i.e. there is a baseline to reset TO.
versionstring | nulloptionalADR-0008 version token of the STORED row a save to this item compares against, at this read's scope (the environment's partition and ?package=) and lifecycle (?state=draft reads the draft row, the plain read the active row). The same keyed token a save receipt's version serves for that row: send it back as the If-Match request header on PUT /meta/:type/:name (with ?mode=draft for the draft) and a concurrent edit is refused 409 METADATA_CONFLICT instead of overwritten. null when no stored row is there — an item served from code or a package artifact, or from a row in another scope (a package-less row a package read falls back to) — so the next save creates one: pin it with If-None-Match: *. ABSENT on the cached published-value branch and on a ?preview=draft read, which publish no version: absence means "not published here", never "no row". Opaque — echo it verbatim, never parse it.
registration{ state: 'registered' } | { state: 'refused'; reason: object } | { state: 'unregistered' }optionalThe automation engine's verdict on this flow (see FlowRegistrationSchema): registered, or refused with the engine's reason verbatim, or unregistered. Present only on a flow read answered by a process that runs an automation service; absent otherwise, and absence means no engine judged it, never registered. Unlike the protection keys it is published by the cached branch too, read live and never part of the ETag, so a cached read that carries it is never answered 304; it is absent on a draft read, whose pending body no engine holds. The verdict is the answering process's engine. Beside item, never inside it: the item's status stays what its author declared.

Nested Shape: GetMetaItemResponse.sortability

PropertyTypeRequiredDescription
fieldsRecord<string, { sortable: boolean; reason?: 'virtual-type'; caveat?: 'unprovisioned-anchor' }>✅Verdict per sortable-addressable column, keyed by field name. The domain is the served field map plus the always-provisioned id, less any field the caller's own field permission withholds: a caller exempt from the object-schema mask (ADR-0106 D4) is served such fields in item and gets no entry for them; for every other caller the served map already excludes them. A name absent from this map (an unknown field, a dotted path, an unprovisioned audit column, a field the caller may not read) has no platform sort behind it and must get no sort affordance.

Nested Shape: GetMetaItemResponse.registration[state='registered']

PropertyTypeRequiredDescription
state'registered'✅The engine holds a registration under the flow's name.

Nested Shape: GetMetaItemResponse.registration[state='refused']

PropertyTypeRequiredDescription
state'refused'✅The engine refused to register the stored flow and holds nothing under its name: it never runs.
reason{ code?: string; message: string }✅What the engine threw, verbatim.

Nested Shape: GetMetaItemResponse.registration[state='unregistered']

PropertyTypeRequiredDescription
state'unregistered'✅The engine holds nothing under the flow's name and recorded no refusal for it.

GetMetaItemsRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name (e.g., "object", "plugin")
packageIdstringoptionalOptional package ID to filter items by
previewDraftsbooleanoptionalDraft-visibility switch (ADR-0033 draft-overlay preview): when true, pending state='draft' rows are overlaid on the active list — draft wins on name collision, draft-only items appear, and each overlaid item is tagged _draft: true so UIs can badge the preview. Absent/false = published world only. Declaration ≠ authorization: this member only switches which rows are read, and ADR-0106 masking is unaffected — callers without draft-preview authorization are refused upstream (admin-gated), not by this schema.

GetMetaItemsResponse

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
itemsany[]✅Array of metadata items
registrationsRecord<string, { state: 'registered' } | { state: 'refused'; reason: object } | { state: 'unregistered' }>optionalThe automation engine's verdict on each listed flow, keyed by the item's name: one entry per listed item that is not a pending draft. Present only on a flow list answered by a process that runs an automation service; absent otherwise, and absence means no engine judged them, never registered. The verdict is the answering process's engine. Beside the items, never inside one: an item's status stays what its author declared.

Nested Shape: GetMetaItemsResponse.registrations[string][state='registered']

PropertyTypeRequiredDescription
state'registered'✅The engine holds a registration under the flow's name.

Nested Shape: GetMetaItemsResponse.registrations[string][state='refused']

PropertyTypeRequiredDescription
state'refused'✅The engine refused to register the stored flow and holds nothing under its name: it never runs.
reason{ code?: string; message: string }✅What the engine threw, verbatim.

Nested Shape: GetMetaItemsResponse.registrations[string][state='unregistered']

PropertyTypeRequiredDescription
state'unregistered'✅The engine holds nothing under the flow's name and recorded no refusal for it.

GetMetaTypesRequest

Properties

PropertyTypeRequiredDescription

GetMetaTypesResponse

Properties

PropertyTypeRequiredDescription
typesstring[]✅Available metadata type names (e.g., "object", "plugin", "view")
entries{ type: string; label: string; description?: string; filePatterns: string[]; … }[]optionalEnriched per-type registry entries (Phase 3a)

Nested Shape: GetMetaTypesResponse.entries[number]

PropertyTypeRequiredDescription
typestring✅Singular type identifier
labelstring✅Human-readable label
descriptionstringoptionalBrief description
filePatternsstring[]✅Glob patterns used to discover artifacts of this type
supportsOverlayboolean✅Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read
allowEnvironmentOverlayboolean✅True when the registry grants environment overlays of this type's packaged items, or when OS_METADATA_WRITABLE names the type (overrideSource env), which opens the type's writes of items no managed package ships and never a managed item (ADR-0131 D6)
allowRuntimeCreateboolean✅New artifacts of this type can be created via runtime API
supportsVersioningboolean✅History is tracked for this type
executionPinnedboolean✅Runtime transactions pin a specific historical version_hash (ADR-0009)
loadOrderinteger✅Loading priority (lower = earlier)
domainEnum<'data' | 'ui' | 'automation' | 'system' | 'security' | 'ai'>✅Protocol domain
overrideSourceEnum<'registry' | 'env'>✅Whether allowEnvironmentOverlay comes from the static registry or is elevated by the OS_METADATA_WRITABLE environment variable
createSeedanyoptionalAuthoritative minimal valid create seed for this type — Studio/CLI/API derive create defaults from it (single source of truth in @objectstack/spec). Absent for canvas-create types whose shape is built interactively.

GetNotificationPreferencesRequest

Properties

PropertyTypeRequiredDescription

GetNotificationPreferencesResponse

Properties

PropertyTypeRequiredDescription
preferences{ email?: boolean; push?: boolean; inApp?: boolean; digest?: Enum<'none' | 'daily' | 'weekly'>; … }✅Current notification preferences

Nested Shape: GetNotificationPreferencesResponse.preferences

PropertyTypeRequiredDescription
emailbooleanoptional (default: true)Receive email notifications
pushbooleanoptional (default: true)Receive push notifications
inAppbooleanoptional (default: true)Receive in-app notifications
digestEnum<'none' | 'daily' | 'weekly'>optional (default: "none")Email digest frequency
channelsRecord<string, { enabled?: boolean; email?: boolean; push?: boolean }>optionalPer-channel notification preferences

GetObjectPermissionsRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name to get permissions for

GetObjectPermissionsResponse

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
permissions{ allowCreate?: boolean; allowRead?: boolean; allowEdit?: boolean; allowDelete?: boolean; … }✅Object-level permissions
fieldPermissionsRecord<string, { readable?: boolean; editable?: boolean }>optionalField-level permissions keyed by field name

Nested Shape: GetObjectPermissionsResponse.permissions

PropertyTypeRequiredDescription
allowCreatebooleanoptional (default: false)Create permission
allowReadbooleanoptional (default: false)Read permission
allowEditbooleanoptional (default: false)Edit permission
allowDeletebooleanoptional (default: false)Delete permission
allowExportbooleanoptionalUser-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords.
allowTransferbooleanoptional (default: false)[RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id)
allowRestoreneveroptional[REMOVED] objects.<object>.allowRestore was removed in @objectstack/spec 17 (ADR-0049) — the restore ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched restore stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
allowPurgeneveroptional[REMOVED] objects.<object>.allowPurge was removed in @objectstack/spec 17 (ADR-0049) — the purge ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched purge stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
viewAllRecordsbooleanoptional (default: false)View All Data (Bypass Sharing)
modifyAllRecordsbooleanoptional (default: false)Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies.
readScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Read depth: own|unit|unit_and_below|org
writeScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Write depth: own|unit|unit_and_below|org

Nested Shape: GetObjectPermissionsResponse.fieldPermissions[string]

PropertyTypeRequiredDescription
readablebooleanoptional (default: true)Field read access
editablebooleanoptional (default: false)Field edit access

GetPackageRequest

Get package request

Properties

PropertyTypeRequiredDescription
idstring✅Package identifier

GetPackageResponse

Get package response

Properties

PropertyTypeRequiredDescription
package{ manifest: object; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; installedAt?: string; … }✅Package details

Nested Shape: GetPackageResponse.package

PropertyTypeRequiredDescription
manifest{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }✅Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema
statusEnum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>optional (default: "installed")Package state: installed, disabled, installing, upgrading, uninstalling, or error
enabledbooleanoptional (default: true)Whether the package is currently enabled
installedAtstringoptionalInstallation timestamp
updatedAtstringoptionalLast update timestamp
installedVersionstringoptionalCurrently installed version for quick access
previousVersionstringoptionalVersion before the last upgrade
statusChangedAtstringoptionalStatus change timestamp
errorMessagestringoptionalError message when status is error
settingsRecord<string, any>optionalUser-provided configuration settings
upgradeHistory{ fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[]optionalVersion upgrade history
registeredNamespacesstring[]optionalNamespace prefixes registered by this package

GetPresenceRequest

Properties

PropertyTypeRequiredDescription
channelstring✅Channel to get presence for

GetPresenceResponse

Properties

PropertyTypeRequiredDescription
channelstring✅Channel name
members{ userId: string; status: Enum<'online' | 'away' | 'busy' | 'offline'>; lastSeen: string; metadata?: Record<string, any> }[]✅Active members and their presence state

Nested Shape: GetPresenceResponse.members[number]

PropertyTypeRequiredDescription
userIdstring✅User identifier
statusEnum<'online' | 'away' | 'busy' | 'offline'>✅Current presence status
lastSeenstring✅ISO 8601 datetime of last activity
metadataRecord<string, any>optionalCustom presence data (e.g., current page, custom status)

GetPublishedMetaItemResponse

The published metadata item body, opaque by ruling (1C). Shape is the item's own metadata-type schema, resolved at read time — never frozen into this contract.


GetTranslationsRequest

Properties

PropertyTypeRequiredDescription
localestring✅BCP-47 locale code

GetTranslationsResponse

Properties

PropertyTypeRequiredDescription
localestring✅Locale code
translations{ objects?: Record<string, object>; picklists?: Record<string, object>; apps?: Record<string, object>; messages?: Record<string, string>; … }✅Translation data

Nested Shape: GetTranslationsResponse.translations

PropertyTypeRequiredDescription
objectsRecord<string, { label?: string; pluralLabel?: string; description?: string; fields?: Record<string, object>; … }>optionalObject translations keyed by object name
picklistsRecord<string, { label?: string; options: Record<string, string> }>optionalPicklist translations keyed by picklist name
appsRecord<string, { label: string; description?: string; navigation?: Record<string, object> }>optionalApp translations keyed by app name
messagesRecord<string, string>optionalUI message translations keyed by message ID
globalActionsRecord<string, { label?: string; description?: string; confirmText?: string; successMessage?: string; … }>optionalGlobal action translations keyed by action name
dashboardsRecord<string, { label?: string; description?: string; actions?: Record<string, object>; widgets?: Record<string, object>; … }>optionalDashboard translations keyed by dashboard name
datasetsRecord<string, { label?: string; description?: string; dimensions?: Record<string, object>; measures?: Record<string, object> }>optionalAnalytics dataset translations keyed by dataset name
pagesRecord<string, { label?: string; description?: string; title?: string; subtitle?: string; … }>optionalPage translations keyed by page name
flowsRecord<string, { label?: string; successMessage?: string; errorMessage?: string; screens?: Record<string, object>; … }>optionalScreen-flow translations keyed by flow name
metadataFormsRecord<string, { label?: string; description?: string; sections?: Record<string, object>; fields?: Record<string, object> }>optionalTranslations for metadata-type configuration forms keyed by metadata type
settingsCommon{ sourceLabels?: object }optionalCross-namespace Settings UI strings
settingsRecord<string, { title?: string; description?: string; groups?: Record<string, object>; keys?: Record<string, object>; … }>optionalSettings manifest translations keyed by namespace

GetUiViewRequest

Properties

PropertyTypeRequiredDescription
objectstring✅Object name (snake_case)
typeEnum<'list' | 'form'>✅View type

GetUiViewResponse

Properties

PropertyTypeRequiredDescription
namestringoptionalItem name — supplied by the metadata door; for an object-scoped container it is the object name.
labelstring | Record<string, string>optionalHuman-readable label shown in metadata lists.
objectstringoptionalObject this container binds to — how a stack-level views: [...] entry says which object its views belong to; read by getViewsByObject() / GET /meta/view?object=.
list{ name?: string; label?: string | Record<string, string>; type?: Enum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>; data?: object | object | object; … }optional
form{ type?: Enum<'simple' | 'tabbed' | 'wizard' | 'split' | 'drawer' | 'modal'>; layout?: Enum<'vertical' | 'horizontal'>; columns?: integer; title?: string; … }optional
listViewsRecord<string, { name?: string; label?: string | Record<string, string>; type?: Enum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>; data?: object | object | object; … }>optionalAdditional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047)
formViewsRecord<string, { type?: Enum<'simple' | 'tabbed' | 'wizard' | 'split' | 'drawer' | 'modal'>; layout?: Enum<'vertical' | 'horizontal'>; columns?: integer; title?: string; … }>optionalAdditional named form views
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this view.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

Nested Shape: GetUiViewResponse.list

PropertyTypeRequiredDescription
namestringoptionalInternal view name (lowercase snake_case)
labelstring | Record<string, string>optionalDisplay label — the default-language string, or an inline locale map ({ en, "zh-CN" }) resolved at render time
typeEnum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>optional (default: "grid")
data{ provider: 'object'; object: string } | { provider: 'value'; items: any[] } | { provider: 'schema'; schemaId: string; schema?: Record<string, any> }optionalData source configuration (defaults to "object" provider)
columnsstring[] | { field: string; label?: string | Record<string, string>; width?: number; align?: Enum<'left' | 'center' | 'right'>; … }[]✅Fields to display as columns — the PROJECTION of the per-view field composition columns x hiddenFields x fieldOrder: this list is the candidate set AND the baseline order; hiddenFields subtracts from it and fieldOrder orders what survives. hiddenFields and fieldOrder cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's ListView grid derives the object's default columns).
filter{ field: string; operator: Enum<'equals' | 'not_equals' | 'contains' | 'not_contains' | 'icontains' | …>; value?: string | number | boolean | null | (string | number)[] }[]optionalFilter criteria (JSON Rules)
sort{ field: string; order: Enum<'asc' | 'desc'> }[]optional
searchableFieldsstring[]optionalFields enabled for search
filterableFieldsstring[]optionalLegacy shorthand for userFilters.fields — bare field names enabled for end-user filtering. Prefer userFilters
resizablebooleanoptionalEnable column resizing
compactToolbarbooleanoptionalCollapse Group/Color/Density/Hide-fields into a single View settings popover
selection{ type?: Enum<'none' | 'single' | 'multiple'> }optionalRow selection configuration
navigation{ mode?: Enum<'page' | 'drawer' | 'modal' | 'split' | 'popover' | 'new_window' | 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' | 'sm' | 'md' | 'lg' | 'xl' | 'full'>; … }optionalConfiguration for item click navigation (page, drawer, modal, etc.)
pagination{ pageSize?: integer; pageSizeOptions?: integer[] }optionalPagination configuration
kanban{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[] }optionalKanban-board configuration — applies when the view renders as a kanban layout
calendar{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string; … }optionalCalendar configuration — applies when the view renders as a calendar layout
gantt{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }optionalGantt-timeline configuration — applies when the view renders as a gantt layout
gallery{ coverField?: string; coverFit?: Enum<'cover' | 'contain'>; cardSize?: Enum<'small' | 'medium' | 'large'>; titleField?: string; … }optionalGallery/card view configuration
timeline{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }optionalTimeline view configuration
chart{ chartType?: Enum<'bar' | 'line' | 'pie' | 'area' | 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }optionalChart binding — applies when the view renders as a chart. A type: 'chart' view must bind one: it names the ADR-0021 dataset and the measures (values) the chart plots, and there is no default binding
map{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }optionalMap configuration — applies when the view renders as a map layout
tree{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }optionalTree/hierarchy configuration — applies when the view renders as a tree layout
pageNameneveroptional[REMOVED] view.pageName was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the page a type: 'page' view was to mount, and that mount was never built: no renderer read the key, so the named page was never reached and the view drew an empty grid. Delete the key; to put a published page in front of users, give the app a navigation item — { type: 'page', pageName: '<page_name>' } under the app's navigation — which is a different key on a different surface and is the page mount that has always rendered. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
descriptionstring | Record<string, string>optionalView description for documentation/tooltips
sharing{ type?: Enum<'personal' | 'collaborative'>; lockedBy?: string }optionalView sharing and access configuration
requiredPermissionsstring[]optional[ADR-0066] Capabilities a user must ALL hold for this list view or dashboard to be served to them — names that permission sets grant through systemPermissions, the same list as requiredPermissions on an app, a navigation item or an action. Absent or empty: no audience gate. There is no any-of form: to serve one item to several audiences (legal OR admin), declare one capability and grant it to each of their permission sets. The server enforces it: a user who does not hold them all is not listed the item and is refused it by name; a view container's list views are pruned the same way, and so are an object's own list views for a user who may not edit the object.
rowHeightEnum<'compact' | 'short' | 'medium' | 'tall' | 'extra_tall'>optionalRow height / density setting
grouping{ fields: object[] }optionalGroup records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema)
rowColor{ field: string; colors?: Record<string, string> }optionalColor rows based on field value
hiddenFieldsstring[]optionalFields to hide in this specific view — the SUBTRACTION of the per-view field composition columns x hiddenFields x fieldOrder: each name listed here is removed from the columns projection before fieldOrder orders the remainder. A name columns never projected subtracts nothing.
fieldOrderstring[]optionalExplicit field display order for this view — the ORDERING of the per-view field composition columns x hiddenFields x fieldOrder: it sorts what survives columns minus hiddenFields and never adds a field. A surviving column absent from fieldOrder sorts LAST, after every listed one, keeping its columns-relative order; a name listed here that did not survive orders nothing.
rowActionsstring[]optionalActions available for individual row items
bulkActionsstring[]optionalActions available when multiple rows are selected
bulkActionDefs{ name: string; label?: string; icon?: string; variant?: Enum<'primary' | 'secondary' | 'danger' | 'ghost' | 'outline'>; … }[]optionalRich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a patch / 'delete') that no action expresses, or for an operation: 'custom' + execution: 'aggregate' entry that dispatches the action it NAMES once for the whole selection — the renderer injects params._selectedIds: string[] (read that on the server, not recordId) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. batchSize does not apply (the call is never chunked); set maxRecords on defs whose server work is expensive. For the PER-RECORD dispatch use bulkActions: ['<name>'] instead — the bare-string form, promoted with the action's own label, params and visible; a 'custom' def without execution: 'aggregate' has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via ${ctx.selection.ids} / ${ctx.selection.count}.
conditionalFormatting{ condition: string | object; style: Record<string, string> }[]optionalConditional formatting rules for list rows
inlineEditbooleanoptionalAllow inline editing of records directly in the list view
exportOptionsEnum<'csv' | 'xlsx' | 'json'>[] | { formats?: Enum<'csv' | 'xlsx' | 'json'>[]; maxRecords?: integer; includeHeaders?: boolean; fileNamePrefix?: string; … }optionalExport configuration for the list toolbar export menu: { formats?, maxRecords?, includeHeaders?, fileNamePrefix?, streaming? }. A bare format array is the legacy spelling and lifts to { formats: [...] } at parse.
userActions{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }optionalUser action toggles for the view toolbar
appearance{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>[] }optionalAppearance and visualization configuration
tabsneveroptional[REMOVED] view.list.tabs was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — no renderer ever mounted a tab bar for it, so authoring it drew nothing: the tab strip above an object's records is the saved-view switcher (ViewTabBar), which renders one tab per named list view and never read this key. Delete the key, and move each tab you want to a named list view under the object's listViews instead: the tab's name becomes the entry's key, its label the entry's label, and its filter rules join the view's own filter on that entry (copy the view's columns too); a tab whose view already named a list view needs nothing more. Every listViews entry renders as a tab in the switcher. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
addRecord{ enabled?: boolean; position?: Enum<'top' | 'bottom' | 'both'>; mode?: Enum<'inline' | 'form' | 'modal'>; formView?: string }optionalAdd record entry point configuration
showRecordCountbooleanoptionalShow record count at the bottom of the list
allowPrintingbooleanoptionalAllow users to print the view
emptyState{ title?: string | Record<string, string>; message?: string | Record<string, string>; icon?: string; link?: object }optionalEmpty state configuration when no records found
aria{ ariaLabel?: string | Record<string, string>; ariaDescribedBy?: string; role?: string }optionalARIA accessibility attributes for the list view
responsiveneveroptional[REMOVED] view.responsive was removed in @objectstack/spec 17.0.0 (audit close-out) — no renderer ever read it; the grid is responsive by its own layout rules. Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
performanceneveroptional[REMOVED] view.performance was removed in @objectstack/spec 17.0.0 (audit close-out) — no renderer or runtime read it; list-view performance tuning was never implemented. Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
stripedneveroptional[REMOVED] view.striped was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — every measured reader only copied it forward and no renderer ever applied it, so authoring it was a parse-clean no-op. There is no authorable striped-rows switch; delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
borderedneveroptional[REMOVED] view.bordered was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — every measured reader only copied it forward and no renderer ever applied it (the grid frame is the renderer's own constant, not authorable). Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
virtualScrollneveroptional[REMOVED] view.virtualScroll was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — every measured reader only copied it forward and no grid ever virtualized off it; authoring it was a parse-clean no-op. Delete the key; large datasets page via pagination. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
userFilters{ element?: Enum<'dropdown' | 'toggle'>; fields?: object[] }optional

Nested Shape: GetUiViewResponse.form

PropertyTypeRequiredDescription
typeEnum<'simple' | 'tabbed' | 'wizard' | 'split' | 'drawer' | 'modal'>optional (default: "simple")
layoutEnum<'vertical' | 'horizontal'>optionalField layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set columns
columnsintegeroptionalNumber of columns for the form body
titlestringoptionalForm title
descriptionstringoptionalForm description
defaultTabstringoptionalInitially active tab (tabbed forms)
tabPositionEnum<'top' | 'bottom' | 'left' | 'right'>optionalTab strip position (tabbed forms)
allowSkipbooleanoptionalWizard step-gate opt-out: allow entering a later step without submitting the one before it. Navigation freedom, NOT a validation exemption — the final submit still re-validates every step and returns to the first failing one. Default (absent/false): steps unlock in array order as each prior step submits validly. UI admission only, never authorization.
showStepIndicatorbooleanoptionalShow the wizard step indicator (renderer default: shown). Step progress (completed/current/upcoming/invalid) is derived from the step gate — this boolean is the only authorable progress surface.
splitDirectionEnum<'horizontal' | 'vertical'>optionalSplit orientation (split forms)
splitSizenumberoptionalPrimary split panel size, % (split forms)
splitResizablebooleanoptionalWhether the split is resizable (split forms)
drawerSideEnum<'top' | 'bottom' | 'left' | 'right'>optionalDrawer side (drawer forms)
drawerWidthstringoptional[DEPRECATED → size buckets] Drawer width, e.g. "480px". A pixel width cannot be chosen without knowing the client viewport — the renderer derives it.
modalSizeEnum<'sm' | 'default' | 'lg' | 'xl' | 'full'>optionalModal size (modal forms)
data{ provider: 'object'; object: string } | { provider: 'value'; items: any[] } | { provider: 'schema'; schemaId: string; schema?: Record<string, any> }optionalData source configuration (defaults to "object" provider)
sections{ name?: string; label?: string | Record<string, string>; description?: string; collapsible?: boolean; … }[]optional
groups{ name?: string; label?: string | Record<string, string>; description?: string; collapsible?: boolean; … }[]optional[LEGACY ALIAS → sections] Accepted for back-compat and folded onto sections at parse; sections wins when both are present. Prefer sections.
subforms{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]optionalInline master-detail child collections
defaultSortneveroptional[REMOVED] form.defaultSort was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's sort. Delete the key and set the sort on the related list view instead. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
sharing{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }optionalPublic sharing configuration for this form
submitBehavior{ kind: 'thank-you'; title?: string; message?: string } | { kind: 'redirect'; url: string; delayMs?: integer } | { kind: 'continue' } | { kind: 'next-record' }optionalPost-submit behavior. On the redirect arm, url is relative-only and interpolates only declared record fields as {{record.field_name}}, URL-escaped.
buttons{ submit?: object; cancel?: object; reset?: object }optionalForm action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm.
defaultsRecord<string, any>optionalInitial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;).
arianeveroptional[REMOVED] form.aria was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.

Nested Shape: GetUiViewResponse.listViews[string]

PropertyTypeRequiredDescription
namestringoptionalInternal view name (lowercase snake_case)
labelstring | Record<string, string>optionalDisplay label — the default-language string, or an inline locale map ({ en, "zh-CN" }) resolved at render time
typeEnum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>optional (default: "grid")
data{ provider: 'object'; object: string } | { provider: 'value'; items: any[] } | { provider: 'schema'; schemaId: string; schema?: Record<string, any> }optionalData source configuration (defaults to "object" provider)
columnsstring[] | { field: string; label?: string | Record<string, string>; width?: number; align?: Enum<'left' | 'center' | 'right'>; … }[]✅Fields to display as columns — the PROJECTION of the per-view field composition columns x hiddenFields x fieldOrder: this list is the candidate set AND the baseline order; hiddenFields subtracts from it and fieldOrder orders what survives. hiddenFields and fieldOrder cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's ListView grid derives the object's default columns).
filter{ field: string; operator: Enum<'equals' | 'not_equals' | 'contains' | 'not_contains' | 'icontains' | …>; value?: string | number | boolean | null | (string | number)[] }[]optionalFilter criteria (JSON Rules)
sort{ field: string; order: Enum<'asc' | 'desc'> }[]optional
searchableFieldsstring[]optionalFields enabled for search
filterableFieldsstring[]optionalLegacy shorthand for userFilters.fields — bare field names enabled for end-user filtering. Prefer userFilters
resizablebooleanoptionalEnable column resizing
compactToolbarbooleanoptionalCollapse Group/Color/Density/Hide-fields into a single View settings popover
selection{ type?: Enum<'none' | 'single' | 'multiple'> }optionalRow selection configuration
navigation{ mode?: Enum<'page' | 'drawer' | 'modal' | 'split' | 'popover' | 'new_window' | 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' | 'sm' | 'md' | 'lg' | 'xl' | 'full'>; … }optionalConfiguration for item click navigation (page, drawer, modal, etc.)
pagination{ pageSize?: integer; pageSizeOptions?: integer[] }optionalPagination configuration
kanban{ groupByField: string; summarizeField?: string; titleField?: string; columns: string[] }optionalKanban-board configuration — applies when the view renders as a kanban layout
calendar{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string; … }optionalCalendar configuration — applies when the view renders as a calendar layout
gantt{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }optionalGantt-timeline configuration — applies when the view renders as a gantt layout
gallery{ coverField?: string; coverFit?: Enum<'cover' | 'contain'>; cardSize?: Enum<'small' | 'medium' | 'large'>; titleField?: string; … }optionalGallery/card view configuration
timeline{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }optionalTimeline view configuration
chart{ chartType?: Enum<'bar' | 'line' | 'pie' | 'area' | 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }optionalChart binding — applies when the view renders as a chart. A type: 'chart' view must bind one: it names the ADR-0021 dataset and the measures (values) the chart plots, and there is no default binding
map{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }optionalMap configuration — applies when the view renders as a map layout
tree{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }optionalTree/hierarchy configuration — applies when the view renders as a tree layout
pageNameneveroptional[REMOVED] view.pageName was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the page a type: 'page' view was to mount, and that mount was never built: no renderer read the key, so the named page was never reached and the view drew an empty grid. Delete the key; to put a published page in front of users, give the app a navigation item — { type: 'page', pageName: '<page_name>' } under the app's navigation — which is a different key on a different surface and is the page mount that has always rendered. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
descriptionstring | Record<string, string>optionalView description for documentation/tooltips
sharing{ type?: Enum<'personal' | 'collaborative'>; lockedBy?: string }optionalView sharing and access configuration
requiredPermissionsstring[]optional[ADR-0066] Capabilities a user must ALL hold for this list view or dashboard to be served to them — names that permission sets grant through systemPermissions, the same list as requiredPermissions on an app, a navigation item or an action. Absent or empty: no audience gate. There is no any-of form: to serve one item to several audiences (legal OR admin), declare one capability and grant it to each of their permission sets. The server enforces it: a user who does not hold them all is not listed the item and is refused it by name; a view container's list views are pruned the same way, and so are an object's own list views for a user who may not edit the object.
rowHeightEnum<'compact' | 'short' | 'medium' | 'tall' | 'extra_tall'>optionalRow height / density setting
grouping{ fields: object[] }optionalGroup records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema)
rowColor{ field: string; colors?: Record<string, string> }optionalColor rows based on field value
hiddenFieldsstring[]optionalFields to hide in this specific view — the SUBTRACTION of the per-view field composition columns x hiddenFields x fieldOrder: each name listed here is removed from the columns projection before fieldOrder orders the remainder. A name columns never projected subtracts nothing.
fieldOrderstring[]optionalExplicit field display order for this view — the ORDERING of the per-view field composition columns x hiddenFields x fieldOrder: it sorts what survives columns minus hiddenFields and never adds a field. A surviving column absent from fieldOrder sorts LAST, after every listed one, keeping its columns-relative order; a name listed here that did not survive orders nothing.
rowActionsstring[]optionalActions available for individual row items
bulkActionsstring[]optionalActions available when multiple rows are selected
bulkActionDefs{ name: string; label?: string; icon?: string; variant?: Enum<'primary' | 'secondary' | 'danger' | 'ghost' | 'outline'>; … }[]optionalRich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a patch / 'delete') that no action expresses, or for an operation: 'custom' + execution: 'aggregate' entry that dispatches the action it NAMES once for the whole selection — the renderer injects params._selectedIds: string[] (read that on the server, not recordId) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. batchSize does not apply (the call is never chunked); set maxRecords on defs whose server work is expensive. For the PER-RECORD dispatch use bulkActions: ['<name>'] instead — the bare-string form, promoted with the action's own label, params and visible; a 'custom' def without execution: 'aggregate' has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via ${ctx.selection.ids} / ${ctx.selection.count}.
conditionalFormatting{ condition: string | object; style: Record<string, string> }[]optionalConditional formatting rules for list rows
inlineEditbooleanoptionalAllow inline editing of records directly in the list view
exportOptionsEnum<'csv' | 'xlsx' | 'json'>[] | { formats?: Enum<'csv' | 'xlsx' | 'json'>[]; maxRecords?: integer; includeHeaders?: boolean; fileNamePrefix?: string; … }optionalExport configuration for the list toolbar export menu: { formats?, maxRecords?, includeHeaders?, fileNamePrefix?, streaming? }. A bare format array is the legacy spelling and lifts to { formats: [...] } at parse.
userActions{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }optionalUser action toggles for the view toolbar
appearance{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' | 'kanban' | 'gallery' | 'calendar' | 'timeline' | 'gantt' | 'map' | 'chart' | 'tree'>[] }optionalAppearance and visualization configuration
tabsneveroptional[REMOVED] view.list.tabs was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — no renderer ever mounted a tab bar for it, so authoring it drew nothing: the tab strip above an object's records is the saved-view switcher (ViewTabBar), which renders one tab per named list view and never read this key. Delete the key, and move each tab you want to a named list view under the object's listViews instead: the tab's name becomes the entry's key, its label the entry's label, and its filter rules join the view's own filter on that entry (copy the view's columns too); a tab whose view already named a list view needs nothing more. Every listViews entry renders as a tab in the switcher. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
addRecord{ enabled?: boolean; position?: Enum<'top' | 'bottom' | 'both'>; mode?: Enum<'inline' | 'form' | 'modal'>; formView?: string }optionalAdd record entry point configuration
showRecordCountbooleanoptionalShow record count at the bottom of the list
allowPrintingbooleanoptionalAllow users to print the view
emptyState{ title?: string | Record<string, string>; message?: string | Record<string, string>; icon?: string; link?: object }optionalEmpty state configuration when no records found
aria{ ariaLabel?: string | Record<string, string>; ariaDescribedBy?: string; role?: string }optionalARIA accessibility attributes for the list view
responsiveneveroptional[REMOVED] view.responsive was removed in @objectstack/spec 17.0.0 (audit close-out) — no renderer ever read it; the grid is responsive by its own layout rules. Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
performanceneveroptional[REMOVED] view.performance was removed in @objectstack/spec 17.0.0 (audit close-out) — no renderer or runtime read it; list-view performance tuning was never implemented. Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
stripedneveroptional[REMOVED] view.striped was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — every measured reader only copied it forward and no renderer ever applied it, so authoring it was a parse-clean no-op. There is no authorable striped-rows switch; delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
borderedneveroptional[REMOVED] view.bordered was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — every measured reader only copied it forward and no renderer ever applied it (the grid frame is the renderer's own constant, not authorable). Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
virtualScrollneveroptional[REMOVED] view.virtualScroll was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — every measured reader only copied it forward and no grid ever virtualized off it; authoring it was a parse-clean no-op. Delete the key; large datasets page via pagination. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
userFilters{ element?: Enum<'dropdown' | 'toggle'>; fields?: object[] }optional

Nested Shape: GetUiViewResponse.formViews[string]

PropertyTypeRequiredDescription
typeEnum<'simple' | 'tabbed' | 'wizard' | 'split' | 'drawer' | 'modal'>optional (default: "simple")
layoutEnum<'vertical' | 'horizontal'>optionalField layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set columns
columnsintegeroptionalNumber of columns for the form body
titlestringoptionalForm title
descriptionstringoptionalForm description
defaultTabstringoptionalInitially active tab (tabbed forms)
tabPositionEnum<'top' | 'bottom' | 'left' | 'right'>optionalTab strip position (tabbed forms)
allowSkipbooleanoptionalWizard step-gate opt-out: allow entering a later step without submitting the one before it. Navigation freedom, NOT a validation exemption — the final submit still re-validates every step and returns to the first failing one. Default (absent/false): steps unlock in array order as each prior step submits validly. UI admission only, never authorization.
showStepIndicatorbooleanoptionalShow the wizard step indicator (renderer default: shown). Step progress (completed/current/upcoming/invalid) is derived from the step gate — this boolean is the only authorable progress surface.
splitDirectionEnum<'horizontal' | 'vertical'>optionalSplit orientation (split forms)
splitSizenumberoptionalPrimary split panel size, % (split forms)
splitResizablebooleanoptionalWhether the split is resizable (split forms)
drawerSideEnum<'top' | 'bottom' | 'left' | 'right'>optionalDrawer side (drawer forms)
drawerWidthstringoptional[DEPRECATED → size buckets] Drawer width, e.g. "480px". A pixel width cannot be chosen without knowing the client viewport — the renderer derives it.
modalSizeEnum<'sm' | 'default' | 'lg' | 'xl' | 'full'>optionalModal size (modal forms)
data{ provider: 'object'; object: string } | { provider: 'value'; items: any[] } | { provider: 'schema'; schemaId: string; schema?: Record<string, any> }optionalData source configuration (defaults to "object" provider)
sections{ name?: string; label?: string | Record<string, string>; description?: string; collapsible?: boolean; … }[]optional
groups{ name?: string; label?: string | Record<string, string>; description?: string; collapsible?: boolean; … }[]optional[LEGACY ALIAS → sections] Accepted for back-compat and folded onto sections at parse; sections wins when both are present. Prefer sections.
subforms{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]optionalInline master-detail child collections
defaultSortneveroptional[REMOVED] form.defaultSort was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's sort. Delete the key and set the sort on the related list view instead. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
sharing{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }optionalPublic sharing configuration for this form
submitBehavior{ kind: 'thank-you'; title?: string; message?: string } | { kind: 'redirect'; url: string; delayMs?: integer } | { kind: 'continue' } | { kind: 'next-record' }optionalPost-submit behavior. On the redirect arm, url is relative-only and interpolates only declared record fields as {{record.field_name}}, URL-escaped.
buttons{ submit?: object; cancel?: object; reset?: object }optionalForm action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm.
defaultsRecord<string, any>optionalInitial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;).
arianeveroptional[REMOVED] form.aria was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.

Nested Shape: GetUiViewResponse.protection

PropertyTypeRequiredDescription
lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>✅Lock policy — none | no-overlay | no-delete | full.
reasonstring✅User-visible reason shown when the lock blocks an action.
docsUrlstringoptionalOptional URL the Studio banner links to for more context.

HistoryMetaItemRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name
sinceSeqnumberoptionalExclusive lower bound on seq for pagination: only events with seq > sinceSeq are returned. Absent means "from the beginning".
limitnumberoptionalMaximum events to return, oldest first. Forwarded to the repository unclamped and with NO default — absent means the full remaining change log. (Deliberately no declared bounds: unlike the audit twin's [1, 500] clamp, nothing on this path clamps or refuses, so declaring .min()/.max() here would refuse values the shipped verb accepts.)
memberstringoptionalBundle member selector, for a metadata type whose identity is a pair (today only email_template, identified by (name, locale)): the member this request addresses, spelled as its discriminator value (zh-CN). Absent, the canonical member (en-US for email_template), which is what a bare-name read answers; naming the canonical value is the same request. It is not the response translation locale, which ?locale= and Accept-Language choose. Refused with 400 VALIDATION_ERROR on a type that has no members, and for a blank value or one longer than a stored member can be (100 characters). A well-formed member with nothing stored or shipped answers as that door answers an absent item.

HistoryMetaItemResponse

Properties

PropertyTypeRequiredDescription
events{ seq: integer; op: Enum<'create' | 'update' | 'delete' | 'rename' | 'publish' | 'revert'>; ref: object; hash: string | null; … }[]✅The durable change-log for the item, oldest first. See the schema-level note for what an empty array means — and what it never means.

Nested Shape: HistoryMetaItemResponse.events[number]

PropertyTypeRequiredDescription
seqinteger✅Sequence number this write produced in the org log (sys_metadata_history.event_seq) — the token the request's sinceSeq filters on, and the same key the write-verb receipts (SaveMetaItemResponseSchema.seq and siblings) carry.
opEnum<'create' | 'update' | 'delete' | 'rename' | 'publish' | 'revert'>✅Which change-log operation the event records (ADR-0008 §2.4). Closed vocabulary, mirrored from the producer's own enum.
ref{ org: string; type: string; name: string; version?: string }✅Which item the event is about.
hashstring | null✅Content hash of the body this event wrote; null when the event wrote none (op="delete").
parentHashstring | null✅Hash the written version was derived from; null for a first version.
versionintegeroptionalPer-(org,type,name) monotonic lineage counter at this event — the token rollbackMetaItem({ toVersion }) pins. Absent when the row recorded none.
previousNamestringoptionalSet on op="rename": the old machine name.
actorstring | null✅Who wrote this. null = system-initiated (boot sync, migration, scheduled job) — never a sentinel string: consumers that resolve this against sys_user must be able to tell "nobody" from "a user id".
messagestringoptionalOptional commit message recorded with the write.
tsstring✅When the write happened (ISO-8601 string).
sourcestring✅Origin label of the write: "fs", "studio", "rest", "ai", "git-import", …

HttpFindQueryParams

Properties

PropertyTypeRequiredDescription
filterstringoptionalJSON-encoded filter expression (canonical, singular).
filtersstringoptionalJSON-encoded filter expression (deprecated plural alias).
selectstringoptionalComma-separated list of fields to retrieve.
sortstringoptionalSort expression (e.g. "name asc,created_at desc" or "-created_at").
orderBystringoptionalAlias for sort (OData compatibility).
topnumberoptionalMax records to return (limit).
skipnumberoptionalRecords to skip (offset).
expandstringoptionalComma-separated list of lookup/master_detail field names to expand. Resolved to populate array and passed to the engine for batch $in expansion.
searchstringoptionalFull-text search query.
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
countbooleanoptionalInclude total count in response.

InstallPackageRequest

Install package request

Properties

PropertyTypeRequiredDescription
manifest{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }✅Package manifest to install
settingsRecord<string, any>optionalUser-provided settings at install time
enableOnInstallbooleanoptionalWhether to enable immediately after install — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive honours it on the registry row: true enables, false disables, and ABSENT keeps the row's current lifecycle state (a fresh install lands enabled)
platformVersionstringoptionalCurrent platform version for compatibility verification

Nested Shape: InstallPackageRequest.manifest

PropertyTypeRequiredDescription
idstring✅Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm)
namespacestringoptionalShort namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project")
defaultDatasourcestringoptional (default: "default")Default datasource for all objects in this package
versionstring✅Package version (SemVer 2.0.0 — e.g. 1.2.3, 2.0.0-beta.1, 1.0.0+20230101)
typeEnum<'plugin' | 'ui' | 'driver' | 'server' | 'app' | 'theme' | 'agent' | 'objectql' | …>✅Type of package
scopeEnum<'cloud' | 'system' | 'project'>optional (default: "project")Deployment scope: cloud | system | project
installModesEnum<'managed' | 'unmanaged'>[]optionalInstall modes this package permits (ADR-0131 D6): managed | unmanaged. Absent means ['managed'].
namestring✅Human-readable package name
descriptionstringoptionalPackage description
permissions{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }optionalRequired permissions at the AUTHORING stage: the structured plugin block { services, hooks, network, fs } (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 PermissionSet[] collection instead (AssembledPackageBodySchema)
objectsstring[]optionalGlob patterns for ObjectQL schemas files
datasourcesstring[]optionalGlob patterns for Datasource definitions
dependenciesRecord<string, string>optionalPackage dependencies
configurationneveroptional[REMOVED] manifest.configuration was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, properties.*.secret promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in defineStack({ plugins: [new MyPlugin({ … })] }), which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here.
contributes{ kinds?: object[] }optionalPlatform contributions
data{ object: string; externalId?: string | string[]; mode?: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; env?: Enum<'prod' | 'dev' | 'test'>[]; … }[]optionalInitial seed data (prefer top-level data field)
capabilitiesneveroptional[REMOVED] manifest.capabilities was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read implements, provides, requires, extensionPoints or extensions, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level manifest.dependencies, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here.
extensionsneveroptional[REMOVED] manifest.extensions was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: contributes.kinds registers metadata kinds, navigationContributions injects navigation into other packages' apps, and code-level extension happens in the plugin itself (init/start).
navigationContributions{ app: string; group?: string; priority?: integer; items: (object | … +9 more)[] }[]optionalNavigation items this package contributes into apps owned by other packages
loadingneveroptional[REMOVED] manifest.loading was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (strategy, preload, codeSplitting, dynamicImport, initialization, dependencyResolution, hotReload, caching, sandboxing, monitoring) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — defineStack registers them and the kernel runs init then start in an order topologically resolved from each composed plugin's own dependencies / optionalDependencies (resolvePluginOrder); the set is fixed until the process restarts. ⚠️ loading.sandboxing in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and allowedServices gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (manifest.runtime) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the node tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation.
engine{ objectstack: string }optionalPlatform compatibility requirements (legacy; superseded by engines)
engines{ platform?: string; protocol?: string }optionalPlugin compatibility ranges (ADR-0025 §3.2; supersedes engine)
runtimeEnum<'node' | 'sandbox' | 'worker'>optionalPlugin trust tier the plugin declares (ADR-0025 §3.6) — enforced at the cloud marketplace publish gate (unverified publisher requesting node → HTTP 422 + manual review); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares
packagingEnum<'bundled' | 'manifest-deps'>optionalDependency packaging strategy (ADR-0025 §3.3)
mainstringoptionalEntry module of a code-bearing plugin, relative to the plugin root; os plugin build bundles it and writes dist/index.mjs here in the compiled manifest (ADR-0025 §3.4)
integrityRecord<string, string>optionalPer-file content digests of the plugin artifact (ADR-0025 §3.2)

InstallPackageResponse

Install package response

Properties

PropertyTypeRequiredDescription
package{ manifest: object; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; installedAt?: string; … }✅Installed package details
messagestringoptionalInstallation status message
dependencyResolution{ dependencies: object[]; canProceed: boolean; requiredActions: object[]; installOrder: string[]; … }optionalDependency resolution result from install analysis

Nested Shape: InstallPackageResponse.package

PropertyTypeRequiredDescription
manifest{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }✅Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema
statusEnum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>optional (default: "installed")Package state: installed, disabled, installing, upgrading, uninstalling, or error
enabledbooleanoptional (default: true)Whether the package is currently enabled
installedAtstringoptionalInstallation timestamp
updatedAtstringoptionalLast update timestamp
installedVersionstringoptionalCurrently installed version for quick access
previousVersionstringoptionalVersion before the last upgrade
statusChangedAtstringoptionalStatus change timestamp
errorMessagestringoptionalError message when status is error
settingsRecord<string, any>optionalUser-provided configuration settings
upgradeHistory{ fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[]optionalVersion upgrade history
registeredNamespacesstring[]optionalNamespace prefixes registered by this package

Nested Shape: InstallPackageResponse.dependencyResolution

PropertyTypeRequiredDescription
dependencies{ packageId: string; requiredRange: string; resolvedVersion?: string; installedVersion?: string; … }[]✅Resolution result for each dependency
canProceedboolean✅Whether installation can proceed
requiredActions{ type: Enum<'install' | 'upgrade' | 'confirm_conflict'>; packageId: string; description: string }[]✅Actions required before proceeding
installOrderstring[]✅Topologically sorted package IDs for installation
circularDependenciesstring[][]optionalCircular dependency chains detected (e.g. [["A", "B", "A"]])

ListAiConversationsRequest

Properties

PropertyTypeRequiredDescription
agentIdstringoptionalFilter by agent
limitintegeroptionalMaximum conversations to return
cursorstringoptionalThe id of the last conversation on the previous page. The next page starts with the conversation created immediately before it, continuing newest first. Omit it to read the first page. An id that names no conversation of the caller is refused rather than read as the start of the list.

ListAiConversationsResponse

Properties

PropertyTypeRequiredDescription
conversations{ id: string; title?: string; agentId?: string; userId?: string; … }[]✅The caller's conversations, newest first — ordered by creation time, then id, both descending
hasMoreboolean✅Whether at least one more conversation follows this page. When true, send the id of the last conversation in conversations as cursor to read the next page.

Nested Shape: ListAiConversationsResponse.conversations[number]

PropertyTypeRequiredDescription
idstring✅Conversation id
titlestringoptionalTitle / summary
agentIdstringoptionalAgent this conversation is bound to
userIdstringoptionalOwning user
messages({ role: Enum<'system' | 'user' | 'assistant' | 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]✅Message history
createdAtstring✅Creation timestamp (ISO 8601)
updatedAtstring✅Last update timestamp (ISO 8601)
metadataRecord<string, any>optionalConversation metadata

ListAiPendingActionsRequest

Properties

PropertyTypeRequiredDescription
statusEnum<'pending' | 'approved' | 'executed' | 'failed' | 'rejected'>optionalFilter by status
conversationIdstringoptionalFilter by proposing conversation
limitintegeroptionalMax rows (server default 100)

ListAiPendingActionsResponse

Properties

PropertyTypeRequiredDescription
items{ id: string; object_name: string; action_name: string; tool_name: string; … }[]✅Queued actions, newest first
totalnumber✅Number of rows returned (not a total across pages)

Nested Shape: ListAiPendingActionsResponse.items[number]

PropertyTypeRequiredDescription
idstring✅Pending action id
object_namestring✅Object the action targets
action_namestring✅Action name
tool_namestring✅Tool that would execute it
tool_inputstring✅Serialized tool input
statusEnum<'pending' | 'approved' | 'executed' | 'failed' | 'rejected'>✅Lifecycle status
resultstringoptionalSerialized result, once executed
errorstringoptionalFailure message, when status is failed
rejection_reasonstringoptionalReason given at rejection
conversation_idstringoptionalConversation that proposed it
message_idstringoptionalMessage that proposed it
proposed_bystringoptionalActor that proposed it
decided_bystringoptionalActor that approved or rejected it
proposed_atstring✅Proposal timestamp (ISO 8601)
decided_atstringoptionalDecision timestamp (ISO 8601)

ListDraftsResponse

Properties

PropertyTypeRequiredDescription
drafts{ type: string; name: string; member?: string; label: string | Record<string, string> | null; … }[]✅Every pending draft visible to the caller, one row per item.

Nested Shape: ListDraftsResponse.drafts[number]

PropertyTypeRequiredDescription
typestring✅Metadata type name (canonical singular).
namestring✅Item name.
memberstringoptionalThe bundle member this draft is, for a metadata type whose identity is a pair (today only email_template, identified by (name, locale)): its discriminator value (zh-CN), and the canonical member spelled out (en-US). Present on every draft of such a type, so two members of one name are two entries; absent for a type whose identity is its name alone. Pass it back as the by-name doors' member selector (?member=) to read, publish or discard this draft.
labelstring | Record<string, string> | null✅The draft body's own top-level label, as authored: a plain string or an inline locale map (I18nLabel), resolved by the reader. null when the body declares none, or none this shape admits — never the item name standing in for it.
packageIdstring | null✅Package the draft is bound to, null for a package-less draft.
updatedAtstring | null✅Last-touch timestamp of the draft row (ISO-8601 string), null when the row recorded none.
updatedBystring | null✅Who last touched the draft, null when the row recorded none.

ListNotificationsRequest

Properties

PropertyTypeRequiredDescription
readbooleanoptionalFilter by read status
typestringoptionalFilter by notification type
limitnumberoptionalMaximum number of notifications to return — the newest N. Omitted leaves the window to the server, which is not a fixed part of this contract: the platform inbox answers 50 and clamps any requested value into 1..200 rather than refusing it. This endpoint is not paginated — there is no continuation token, so a larger window is the only way to see more.
cursorneveroptional[REMOVED] cursor was removed from GET /api/v1/notifications in @objectstack/spec 17 (ADR-0049) — it was declared on the request AND the response and honoured on neither: the server reads only read/type/limit, and no emit site ever wrote the response key, so a caller paginating by it re-read the first window forever with no error and no 400. Delete the key; the cursor argument of client.notifications.list() was removed with it. This route is NOT paginated — it answers the newest limit notifications and stops, so ask for a bigger window (limit, clamped by the server into 1..200) instead of a next page. A first-class inbox cursor, if ever built, will be a response-minted opaque token, not this key.

ListNotificationsResponse

Properties

PropertyTypeRequiredDescription
notifications{ id: string; type: string; title: string; body: string; … }[]✅List of notifications — the newest window, not a page
unreadCountnumber✅Total number of unread notifications
cursorneveroptional[REMOVED] cursor was removed from GET /api/v1/notifications in @objectstack/spec 17 (ADR-0049) — it was declared on the request AND the response and honoured on neither: the server reads only read/type/limit, and no emit site ever wrote the response key, so a caller paginating by it re-read the first window forever with no error and no 400. Delete the key; the cursor argument of client.notifications.list() was removed with it. This route is NOT paginated — it answers the newest limit notifications and stops, so ask for a bigger window (limit, clamped by the server into 1..200) instead of a next page. A first-class inbox cursor, if ever built, will be a response-minted opaque token, not this key.

Nested Shape: ListNotificationsResponse.notifications[number]

PropertyTypeRequiredDescription
idstring✅Notification ID
typestring✅Notification type
titlestring✅Notification title
bodystring✅Notification body text
readbooleanoptional (default: false)Whether notification has been read
dataRecord<string, any>optionalAdditional notification data
actionUrlstringoptionalURL to navigate to when clicked
createdAtstring✅When notification was created

ListPackagesRequest

List packages request

Properties

PropertyTypeRequiredDescription
statusEnum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>optionalFilter by package status
typeEnum<'plugin' | 'ui' | 'driver' | 'server' | 'app' | 'theme' | 'agent' | 'objectql' | 'module' | 'gateway' | 'adapter'>optionalFilter by package type
enabledbooleanoptionalFilter by enabled state

ListPackagesResponse

List packages response

Properties

PropertyTypeRequiredDescription
packages{ manifest: object; status?: Enum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>; enabled?: boolean; installedAt?: string; … }[]✅List of installed packages
totalnumber✅Total package count

Nested Shape: ListPackagesResponse.packages[number]

Installed package with runtime lifecycle state

PropertyTypeRequiredDescription
manifest{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }✅Package manifest at the AUTHORING stage; a row installed by a defineStack() host carries the assembled body instead — see AssembledInstalledPackageSchema / InstalledPackageAtEitherStageSchema
statusEnum<'installed' | 'disabled' | 'installing' | 'upgrading' | 'uninstalling' | 'error'>optional (default: "installed")Package state: installed, disabled, installing, upgrading, uninstalling, or error
enabledbooleanoptional (default: true)Whether the package is currently enabled
installedAtstringoptionalInstallation timestamp
updatedAtstringoptionalLast update timestamp
installedVersionstringoptionalCurrently installed version for quick access
previousVersionstringoptionalVersion before the last upgrade
statusChangedAtstringoptionalStatus change timestamp
errorMessagestringoptionalError message when status is error
settingsRecord<string, any>optionalUser-provided configuration settings
upgradeHistory{ fromVersion: string; toVersion: string; upgradedAt: string; status: Enum<'success' | 'failed' | 'rolled_back'>; … }[]optionalVersion upgrade history
registeredNamespacesstring[]optionalNamespace prefixes registered by this package

MarkAllNotificationsReadRequest

Properties

PropertyTypeRequiredDescription

MarkAllNotificationsReadResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether the operation succeeded
readCountnumber✅Number of notifications marked as read

MarkNotificationsReadRequest

Properties

PropertyTypeRequiredDescription
idsstring[]✅Notification IDs to mark as read

MarkNotificationsReadResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether the operation succeeded
readCountnumber✅Number of notifications marked as read

MetadataConflictError

Properties

PropertyTypeRequiredDescription
errorstring✅Human-readable refusal sentence, unchanged by currentVersion: it names the item and the version the door judged current. For display and logs; branch on code and read currentVersion, never parse this.
code'METADATA_CONFLICT'✅ADR-0112 registered code of the optimistic-lock refusal.
currentVersionstring | null✅The version token of the head the door judged current — the value the sentence names — or null when no row of the target lifecycle exists. Send it as If-Match to overwrite deliberately (or If-None-Match: * when it is null), or re-read the item to merge first. Opaque — echo it verbatim, never parse it.

Notification

Properties

PropertyTypeRequiredDescription
idstring✅Notification ID
typestring✅Notification type
titlestring✅Notification title
bodystring✅Notification body text
readbooleanoptional (default: false)Whether notification has been read
dataRecord<string, any>optionalAdditional notification data
actionUrlstringoptionalURL to navigate to when clicked
createdAtstring✅When notification was created

NotificationPreferences

Properties

PropertyTypeRequiredDescription
emailbooleanoptional (default: true)Receive email notifications
pushbooleanoptional (default: true)Receive push notifications
inAppbooleanoptional (default: true)Receive in-app notifications
digestEnum<'none' | 'daily' | 'weekly'>optional (default: "none")Email digest frequency
channelsRecord<string, { enabled?: boolean; email?: boolean; push?: boolean }>optionalPer-channel notification preferences

Nested Shape: NotificationPreferences.channels[string]

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Whether this channel is enabled
emailbooleanoptionalOverride email setting
pushbooleanoptionalOverride push setting

PublishMetaItemRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name — lowercase snake_case segments, optionally dot-qualified (crm_lead, crm_lead.pipeline). The promotion door enforces the same grammar as saveMetaItem.
actorstringoptionalIdentity recorded on the op='publish' history event. On the REST door this is the request's authenticated identity (one producer) — never a caller-supplied header.
messagestringoptionalOptional human-readable note recorded with the publish history event.
packageIdstring | nulloptionalADR-0048 — the software package the draft being promoted was listed under, when the caller has one to state (?package=<id> on the REST door). Named, the publish promotes that package's draft and no other. ABSENT names no package: the publish promotes the draft a draft save that names no package writes — the draft of the one package the item's active rows are bound to, else its package-unbound draft, promoted into that package — and answers 404 NO_DRAFT when neither exists, as the strict draft read does. For an item never published into a package it is the package-unbound draft, else the one package's draft. It is refused 409 RESOURCE_CONFLICT when the active rows (or, with none, the drafts) are bound to two or more packages. ⚠️ null is NOT the same as absent: the implementation branches on the KEY BEING PRESENT, and null pins the lookup to the package-UNBOUND row. Spread it in conditionally; a present-and-undefined key coerces to null downstream and makes a package-bound draft unfindable — a silent no_draft.
memberstringoptionalBundle member selector, for a metadata type whose identity is a pair (today only email_template, identified by (name, locale)): the member this request addresses, spelled as its discriminator value (zh-CN). Absent, the canonical member (en-US for email_template), which is what a bare-name read answers; naming the canonical value is the same request. It is not the response translation locale, which ?locale= and Accept-Language choose. Refused with 400 VALIDATION_ERROR on a type that has no members, and for a blank value or one longer than a stored member can be (100 characters). A well-formed member with nothing stored or shipped answers as that door answers an absent item.

PublishMetaItemResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Always true on a 2xx — the draft was promoted. It does NOT cover the best-effort side effects below, each of which reports its own success.
versionstring✅Version token of the row this promotion left, and the token the ADR-0008 optimistic-concurrency chain runs on: send it back as the If-Match request header on the next write to that item and a concurrent edit is refused 409 METADATA_CONFLICT instead of silently overwritten. A keyed digest of the stored content hash, never the hash itself — the same form SaveMetaItemResponseSchema.version and the item read's version serve. Opaque to callers — echo it verbatim, never parse it. Currently emitted as hmac-sha256: plus 64 hex characters, but the format is not part of this contract.
seqinteger✅Monotonic sequence number of the op='publish' metadata event this promotion appended to the item history (sys_metadata_history.event_seq). Orders writes; unlike version it is not an OCC token.
seedApplied{ success: boolean; inserted: integer; updated: integer; error?: string; … }optionalOutcome of materializing a published seed body into data rows. Present ONLY when the published type is seed — publishing a seed is what makes its rows live, so the load rides along with the metadata promotion. Best-effort: a seed-load problem is surfaced here, never thrown, so a caller must check seedApplied.success instead of assuming the 200 covered the data. Absent on the batch path, which suppresses the per-item apply and loads every seed body in one later pass.
materializeApplied{ success: boolean; inserted: integer; updated: integer; error?: string }optionalOutcome of the ADR-0086 P2 publish-time materializer — the step that applies the published body to its data-plane state (e.g. flow, whose draft credentials become the live ones). Present ONLY when a materializer is registered for this metadata type, which is why it is optional: its absence means "no materializer ran", never "it failed". Best-effort, same contract as seedApplied.
projectionApplied{ success: boolean; error?: string }optionalOutcome of the awaited ADR-0094 mutation projector — the post-persist step that materializes this metadata into its derived data-plane read model. The same receipt {@link SaveMetaItemResponseSchema} carries, because the projector runs on BOTH write doors: a direct active save and this draft→active promotion. Present ONLY when a projector is registered for this metadata type. Best-effort — a projector failure is reported here and logged, never thrown.
advisories{ rule: string; path: string; where: string; message: string; … }[]optionalNon-gating findings from the runtime authoring gate — the same shared author-time rules os validate / os build / os lint run, applied to the DRAFT body this promotion carried to active (the same key SaveMetaItemResponseSchema carries, because the gate runs on both write doors, D1). The promotion SUCCEEDED; these are what the gate has to say about it anyway. Present ONLY when at least one advisory was raised — an empty array is never emitted, so a clean publish's response bytes are unchanged and absence means "nothing to report", never "the gate did not run". Advisory by construction: every entry has severity warning or info, because an error finding refuses the promotion and arrives as the 422 invalid_metadata envelope instead of here. A caller that ignores this key behaves exactly as before. This door is the one Studio's designer takes on every edit (draft save, then publish), and a Studio / MCP / AI author has no CLI at all — which is the gap this key exists to close.
messagestringoptionalHuman-readable receipt, e.g. Published draft — type=view, name=cases [seq=3]. The producer sets it on every publish today; it stays optional to match the producer's own signature and its SaveMetaItemResponse twin, and because an absent human-readable string strips no data — the failure mode this key exists to prevent.

Nested Shape: PublishMetaItemResponse.seedApplied

PropertyTypeRequiredDescription
successboolean✅False when the seed rows did not fully land. The publish itself still succeeded — check this rather than assuming data went live.
insertedinteger✅Rows created by the externalId-keyed upsert.
updatedinteger✅Rows updated by the externalId-keyed upsert.
errorstringoptionalSingle failure message, present when the seed apply threw before the loader ran (including "no readable seed bodies").
errorsany[]optionalPer-record failures reported by the seed loader. Present only when the loader ran and returned a non-empty error list.

Nested Shape: PublishMetaItemResponse.materializeApplied

PropertyTypeRequiredDescription
successboolean✅False when the materializer threw or reported failure; the publish still succeeded.
insertedinteger✅Data-plane rows created by the materializer.
updatedinteger✅Data-plane rows updated by the materializer.
errorstringoptionalMaterializer failure message, present only when success is false.

Nested Shape: PublishMetaItemResponse.projectionApplied

PropertyTypeRequiredDescription
successboolean✅False when the projector threw; the metadata promotion itself still succeeded.
errorstringoptionalProjector failure message, present only when success is false.

Nested Shape: PublishMetaItemResponse.advisories[number]

PropertyTypeRequiredDescription
rulestring✅Stable diagnostic rule id (flow-multi-write-unfiltered, approval-expression-invalid, …). Machine-readable and stable across releases — the key a renderer groups or suppresses by.
pathstring✅Config path inside the SUBMITTED body (flows[0].nodes[1].config.multi), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. The TOP-LEVEL collection entry follows one rule. The gate evaluates against a private per-write snapshot whose indexes no caller can resolve; a collection-resident write type is one whose collection that snapshot fills with the tenant's other stored items beside the written one, and there the entry is keyed by NAME, never by an array index — for example objects.acme_invoice.sharingModel (an object write) or datasets.acme_invoice_metrics.dimensions[0].field (a dataset write). Every other write type is the sole member of its own collection in that snapshot, so its [0] IS the written item and stays positional (for example flows[0]...). Which write types are collection-resident is derived, never listed: @objectstack/lint's runtime gate computes the set (NAME_KEYED_STACK_KEYS) as the collections it carries as resolution context that a gated write type also lands in, so it changes with the gate, not with this text. An entry whose name cannot be spliced into a dotted path keeps its index. Nested positions inside one named item stay positional (objects.acme_invoice.indexes[1]): they index the author's own document.
wherestring✅Human-readable location — flow "leave_approval" · node "approve". Prose for a person; use path for anything mechanical.
messagestring✅What is wrong, in the rule author's own words.
hintstring✅How to fix it.
severityEnum<'error' | 'warning' | 'info'>✅How the gate treated this finding. error means the write was REFUSED (these appear on the 422, never on a 2xx); warning / info are advisory — the write succeeded and the finding is FYI.

PublishPackageDraftsResponse

Properties

PropertyTypeRequiredDescription
successboolean✅True only when every pending draft promoted (failed empty) AND at least one item published. A pre-flight refusal or an ADR-0067 D2 rollback answers false on a 200 — but so does a publish with nothing to promote, so false alone is NOT a refusal: read outcome, not this boolean or the HTTP status. Always equal to outcome === 'published' (pinned). It does NOT cover the best-effort receipts below, each of which reports its own success.
outcomeEnum<'published' | 'refused' | 'nothing_to_publish'>✅First-class discriminant for WHICH exit answered — the fact success compresses into one boolean. published: at least one draft promoted and none refused. refused: the batch was refused — a pre-flight violation or the ADR-0067 D2 all-or-nothing rollback; the per-item story is in failed[], which is non-empty exactly on this outcome (the invariant consumers previously had to reverse-engineer, now stated by the producer). nothing_to_publish: the package had no pending drafts — nothing landed AND nothing was refused; success stays false (a no-op is not a successful publish), which before this field made that answer indistinguishable from a refusal. Producer invariants, pinned in the conformance suites: success === (outcome === 'published'); refused if and only if failed.length > 0; nothing_to_publish if and only if published.length === 0 && failed.length === 0. Values are lowercase snake, matching the sys_metadata_audit outcome vocabulary.
publishedCountinteger✅Number of drafts promoted to active — published.length. 0 on every refusal path (the batch is all-or-nothing, ADR-0067 D2).
failedCountinteger✅Number of items that did not publish — failed.length. On a rollback this counts the WHOLE batch: the causal item plus every sibling marked BATCH_ABORTED.
published{ type: string; name: string; version: string; advisories?: object[] }[]✅Every draft promoted to active, in publish order. Empty on every refusal path.
failed{ type: string; name: string; error: string; code?: string; … }[]✅Items that did not publish. Because the batch is all-or-nothing (ADR-0067 D2), a non-empty list means NOTHING landed: published: [], publishedCount: 0.
seedApplied{ success: boolean; inserted?: integer; updated?: integer; error?: string; … }optionalAggregate outcome of materializing EVERY published seed body in one multi-pass loader run (cross-seed references need the whole set). Present ONLY when the batch published at least one seed. Two producers, one key: the batch itself self-applies (applySeedBodies), and the REST door back-fills the same key for custom protocols that do not — never both (an externalId-less seed would double-insert). Best-effort: a seed problem is surfaced here, never thrown.
materializeApplied{ success: boolean; inserted: integer; updated: integer; failures: object[] }optionalADR-0086 P2 — aggregate result of publish-time materializers across the batch (e.g. flow, whose draft credentials become the live ones), including side-effect failures surfaced by the per-item effects loop. Present ONLY when at least one published item had a registered materializer or a side-effect failure. Best-effort, same contract as seedApplied.
probesanyoptionalADR-0038 L3 post-publish runtime probe report — one real read per published artifact (seeded objects have rows, views are readable, widget dataset selections execute). DELIBERATELY OPAQUE in this contract: the key is declared and carried through verbatim, but its inner shape is intentionally not modeled until a consumer needs a field of it. Present only when something was publishable; probes never fail the publish.
commitIdstringoptionalADR-0067 — id of the commit this publish recorded. Absent when nothing published.
unhiddenAppsstring[]optionalADR-0045 §3 — names of the apps whose _unpublished gate this publish cleared (publish = live AND visible; a materialized additive build has no drafts, only this flip). Attached by the REST door, not the protocol helper. Present ONLY when at least one app flipped — on a mid-loop failure it names the apps that DID persist, beside unhideError ('s split report). Spelling is a permanent wire contract (see the door's note).
unhideErrorstringoptionalPresent when the ADR-0045 visibility flip failed (wholly or partway): the drafts ARE published, but apps still stored _unpublished: true stay externally unobservable. Client-facing text only — undeclared driver text is withheld per ADR-0112; the full cause is in the server log. The route is idempotent: re-run it once the cause is resolved.
rebindErrorstringoptionalPresent when the post-publish metadata:reloaded announce failed: everything is published and stored, but boot-cached consumers keep the pre-publish view until re-run or restart (a newly published record-triggered flow does not bind its trigger). Client-facing text only, same ADR-0112 withhold as unhideError.

Nested Shape: PublishPackageDraftsResponse.published[number]

PropertyTypeRequiredDescription
typestring✅Metadata type of the promoted draft (canonical singular).
namestring✅Item name of the promoted draft.
versionstring✅Version token of the promoted row — the same keyed ADR-0008 optimistic-concurrency token the single-item doors return: echo it back as If-Match on the next write to this item. A keyed digest of the stored content hash, never the hash itself. Opaque to callers; currently hmac-sha256: plus 64 hex characters, but the format is not part of this contract.
advisories{ rule: string; path: string; where: string; message: string; … }[]optionalNon-gating findings the runtime authoring gate raised against THIS draft's promotion (the same element shape and the same omitted-when-empty discipline as PublishMetaItemResponseSchema.advisories, riding each element rather than a parallel top-level map). Present ONLY when at least one finding was raised — an empty array is never emitted, so an advisory-free batch's response bytes are unchanged. Advisory by construction: every entry is warning/info, because an error finding refuses the promotion and — the batch being all-or-nothing — aborts the whole batch as failed[] instead; failed[] elements never carry this key.

Nested Shape: PublishPackageDraftsResponse.failed[number]

PropertyTypeRequiredDescription
typestring✅Metadata type of the item that did not publish.
namestring✅Item name.
errorstring✅What refused it. On a rollback, the causal item carries its real error and every sibling carries the all-or-nothing explanation. A refusal that produced structured findings states a one-sentence HEADLINE here (what failed, where, which rules, how many); the per-path detail rides issues[] instead of being restated in this string (consumers rendering both channels were showing every finding twice).
codestringoptionalMachine code for the refusal class (SCREAMING_SNAKE, ADR-0112 vocabulary) — e.g. a pre-flight violation code, or BATCH_ABORTED on the non-causal items of a rolled-back batch.
issues{ rule: string; path: string; where: string; message: string; … }[]optionalThe structured findings behind the refusal, when the refusing error carried them — today the author-time gate's INVALID_METADATA refusal on the causal item. The producer has emitted this key; declaring it is what lets a typed consumer read it back, and what lets error stay a headline without losing the per-path detail. Same element shape as published[].advisories and the single-item 422's issues[] (— one dialect, declared once). Present ONLY on the causal item and only when the refusal produced structured findings; BATCH_ABORTED siblings never carry it. Absent means "this refusal carried no structured findings", never "no problems".

Nested Shape: PublishPackageDraftsResponse.seedApplied

PropertyTypeRequiredDescription
successboolean✅False when the seed rows did not fully land. The publish itself still succeeded — check this rather than assuming data went live.
insertedintegeroptionalRows created by the externalId-keyed upsert. Optional ONLY because the route-level fallback producer (custom protocols that do not self-apply) reports early failures without counters; the in-batch producer always emits both counters.
updatedintegeroptionalRows updated by the externalId-keyed upsert. Same optionality rationale as inserted.
errorstringoptionalSingle failure message, present when the apply failed before the loader ran (including "no readable seed bodies"). When the failure is the seed bodies' own schema refusal, this is a one-sentence headline and the per-path detail rides issues[].
errorsany[]optionalPer-record failures reported by the seed loader, plus any seed-body read failures. May be present and empty on a clean load.
issues{ path: string; message: string; code?: string }[]optionalStructured spec-validation findings behind error, present when the apply was refused by the seed bodies' own schema — the declared 422 seedRequestValidationError mints. The per-path detail lives HERE, once; error stays a one-sentence headline. Absent on non-validation failures (driver faults, unreadable bodies), whose whole story is error / errors[].

Nested Shape: PublishPackageDraftsResponse.materializeApplied

PropertyTypeRequiredDescription
successboolean✅False when any item's materializer failed; the publish still succeeded.
insertedinteger✅Data-plane rows created across the batch.
updatedinteger✅Data-plane rows updated across the batch.
failures{ type: string; name: string; error: string }[]✅Each item whose data-plane projection did NOT land — named per-item, unlike the single-item door's scalar error, because one aggregate boolean over N items would hide WHICH ones never went live.

RealtimeConnectRequest

Properties

PropertyTypeRequiredDescription
transportEnum<'websocket' | 'sse' | 'polling'>optionalPreferred transport protocol
channelsstring[]optionalChannels to subscribe to on connect
tokenstringoptionalAuthentication token

RealtimeConnectResponse

Properties

PropertyTypeRequiredDescription
connectionIdstring✅Unique connection identifier
transportEnum<'websocket' | 'sse' | 'polling'>✅Negotiated transport protocol
urlstringoptionalWebSocket/SSE endpoint URL

RealtimeDisconnectRequest

Properties

PropertyTypeRequiredDescription
connectionIdstringoptionalConnection ID to disconnect

RealtimeDisconnectResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether disconnection succeeded

RealtimeSubscribeRequest

Properties

PropertyTypeRequiredDescription
channelstring✅Channel name to subscribe to
eventsstring[]optionalSpecific event types to listen for
filterRecord<string, any>optionalEvent filter criteria

RealtimeSubscribeResponse

Properties

PropertyTypeRequiredDescription
subscriptionIdstring✅Unique subscription identifier
channelstring✅Subscribed channel name

RealtimeUnsubscribeRequest

Properties

PropertyTypeRequiredDescription
subscriptionIdstring✅Subscription ID to cancel

RealtimeUnsubscribeResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether unsubscription succeeded

RegisterDeviceRequest

Properties

PropertyTypeRequiredDescription
tokenstring✅Device push notification token
platformEnum<'ios' | 'android' | 'web'>✅Device platform
deviceIdstringoptionalUnique device identifier
namestringoptionalDevice friendly name

RegisterDeviceResponse

Properties

PropertyTypeRequiredDescription
deviceIdstring✅Registered device ID
successboolean✅Whether registration succeeded

RejectAiPendingActionResponse

Properties

PropertyTypeRequiredDescription
status'rejected'✅Always "rejected"
idstring✅The rejected action id

RollbackMetaItemResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether the rollback landed.
versionstring✅The new live row's ADR-0008 optimistic-concurrency token — the same carrier saveItem returns; pass it back as options.ifMatch.
seqnumber✅The new live row's history sequence number.
restoredFromVersionnumber✅Which history version was restored.
messagestringoptionalRollback note, when one was recorded.

RuntimeAuthoringIssue

Properties

PropertyTypeRequiredDescription
rulestring✅Stable diagnostic rule id (flow-multi-write-unfiltered, approval-expression-invalid, …). Machine-readable and stable across releases — the key a renderer groups or suppresses by.
pathstring✅Config path inside the SUBMITTED body (flows[0].nodes[1].config.multi), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. The TOP-LEVEL collection entry follows one rule. The gate evaluates against a private per-write snapshot whose indexes no caller can resolve; a collection-resident write type is one whose collection that snapshot fills with the tenant's other stored items beside the written one, and there the entry is keyed by NAME, never by an array index — for example objects.acme_invoice.sharingModel (an object write) or datasets.acme_invoice_metrics.dimensions[0].field (a dataset write). Every other write type is the sole member of its own collection in that snapshot, so its [0] IS the written item and stays positional (for example flows[0]...). Which write types are collection-resident is derived, never listed: @objectstack/lint's runtime gate computes the set (NAME_KEYED_STACK_KEYS) as the collections it carries as resolution context that a gated write type also lands in, so it changes with the gate, not with this text. An entry whose name cannot be spliced into a dotted path keeps its index. Nested positions inside one named item stay positional (objects.acme_invoice.indexes[1]): they index the author's own document.
wherestring✅Human-readable location — flow "leave_approval" · node "approve". Prose for a person; use path for anything mechanical.
messagestring✅What is wrong, in the rule author's own words.
hintstring✅How to fix it.
severityEnum<'error' | 'warning' | 'info'>✅How the gate treated this finding. error means the write was REFUSED (these appear on the 422, never on a 2xx); warning / info are advisory — the write succeeded and the finding is FYI.

SaveMetaItemRequest

Properties

PropertyTypeRequiredDescription
typestring✅Metadata type name
namestring✅Item name — lowercase snake_case segments, optionally dot-qualified (crm_lead, crm_lead.pipeline). Slash-compound names are refused at the publish door.
itemany✅Metadata item definition
parentVersionstring | nulloptionalADR-0008 optimistic-concurrency pin: the version token the caller believes is current (on the REST door, the If-Match request header; the item read's version and a save receipt's version serve it). Present as a string, a concurrent edit is reported as a 409 conflict instead of silently overwritten. ⚠️ null is NOT the same as absent: a present null asserts "no current row of this lifecycle" — the first-write pin, refused 409 when a row already exists (on the REST door, If-None-Match: *; that header takes * alone and never beside If-Match, and any other spelling is refused 400) — while an ABSENT key is unpinned: the implementation adopts the current row's hash as the parent (last-write-wins). Nullable because that is the implementation's parameter type, and unlike the reset twin (which folds a present null back to the current hash) this verb passes null through to the repository's conflict check unchanged.
actorstringoptionalIdentity recorded on the write's history event (recorded_by, a lookup into sys_user) and audit row. On the REST door this is the request's authenticated identity (one producer) — never a caller-supplied header. Absent, the event is recorded actor-less (null), deliberately not attributed to "system".
forcebooleanoptionalDestructive-change acknowledgement (?force=true on the REST door): skips the safety diff that refuses an object save whose body drops fields or narrows types the stored item still carries (409 with the findings otherwise). Only object saves reach that diff, so the flag is inert for every other type. Absent = the guard runs.
modeEnum<'draft' | 'publish'>optionalPer-item lifecycle (ADR-0005 drafts): draft stages the body as a pending draft overlay (?mode=draft on the REST door; the publish door promotes it later); publish or ABSENT writes straight to the live active row — the legacy default, kept so callers that predate the draft/publish split keep working. Any value other than draft is read as publish.
packageIdstring | nulloptionalADR-0048 — the software package to bind the saved row to (sys_metadata.package_id; ?package=<id> on the REST door, where all and an empty value name no package). Set when authoring inside a Studio package workspace; a named read-only base package is refused. On create the row is stamped with this id; on update an existing binding is preserved, never silently re-bound. Absent or null names no package. An active save then writes the row of the one package the item's active rows are bound to, as a save naming that package does, is refused 409 RESOURCE_CONFLICT when they are bound to two or more, and with none writes the env-local overlay (no package stamp); a draft save writes the draft of the package the item's active row is bound to when it is package-bound (a pending change stays in the package it edits), else the env-local draft. The row written is also the one the parent-version resolution reads: an unpinned save takes its version as the parent (last-write-wins), a pinned one is judged against it, and the item read at the same scope serves it as version.
writeFaceEnum<'package-duplicate' | 'meta-envelope' | 'meta-dispatch' | 'external-import'>optionalWhich write door a refusal is being rendered FOR — stated by the SERVER, never by a remote caller: every door builds this request field by field and never spreads a request body into it, so there is no path for a client to smuggle a face in, and a face arriving in a wire body is simply never read. Two refusals branch on it, for different questions: the 409 destructive-change remedy names the acknowledgement mechanism that actually exists on the refusing door (?force=true on the REST doors; the dispatcher, the duplicate-package door and the external-table import have none), and the 422 invalid-metadata message adapts to whether a structured issues[] channel reaches the consumer beside it. Absent = the conservative default wording.

SaveMetaItemResponse

Properties

PropertyTypeRequiredDescription
successboolean✅
versionstring✅Version token of the row this write left, and the token the ADR-0008 optimistic-concurrency chain runs on: send it back as the If-Match request header on the next write to that item and a concurrent edit is refused 409 METADATA_CONFLICT instead of silently overwritten. The item read serves the same token for the same row (GetMetaItemResponseSchema.version). A keyed digest of the stored content hash, never the hash itself (under the registered crypto provider's key, or a process-scoped key while none is registered). Opaque to callers — echo it verbatim, never parse it. Currently emitted as hmac-sha256: plus 64 hex characters, but the format is not part of this contract.
seqinteger✅Monotonic sequence number of the metadata event this write appended to the item history (sys_metadata_history.event_seq). Orders writes; unlike version it is not an OCC token.
stateEnum<'draft' | 'active'>✅Lifecycle the body was written into: "draft" when the request asked for draft mode (?mode=draft), otherwise "active" (published and live). A draft is staged only — it is not served to the runtime until published.
projectionApplied{ success: boolean; error?: string }optionalOutcome of the awaited ADR-0094 mutation projector — the post-persist step that materializes this metadata into its derived data-plane read model (e.g. datasource → its live connection pool). Present ONLY when a projector is registered for this metadata type, which is why it is optional: its absence means "no projector ran", never "the projection failed". Best-effort by design — a projector failure is reported here and logged, never thrown, so a caller that needs the read model to be live must check projectionApplied.success rather than rely on the 200.
advisories{ rule: string; path: string; where: string; message: string; … }[]optionalNon-gating findings from the runtime authoring gate — the same shared author-time rules os validate / os build / os lint run, applied to this body on its way to active. The write SUCCEEDED; these are what the gate has to say about it anyway (closing D3). Present ONLY when at least one advisory was raised — an empty array is never emitted, so a clean save's response bytes are unchanged and absence means "nothing to report", never "the gate did not run". Advisory by construction: every entry has severity warning or info, because an error finding refuses the write and arrives as the 422 invalid_metadata envelope instead of here. A caller that ignores this key behaves exactly as before. Runtime-only: the CLI surfaces the same findings on its own stdout, and a Studio / MCP / AI author has no CLI at all, which is the gap this key exists to close. The gate runs on both write doors (D1), and both report: POST /meta/:type/:name/publish carries the same key on PublishMetaItemResponseSchema.
messagestringoptional

Nested Shape: SaveMetaItemResponse.projectionApplied

PropertyTypeRequiredDescription
successboolean✅False when the projector threw; the metadata write itself still succeeded.
errorstringoptionalProjector failure message, present only when success is false.

Nested Shape: SaveMetaItemResponse.advisories[number]

PropertyTypeRequiredDescription
rulestring✅Stable diagnostic rule id (flow-multi-write-unfiltered, approval-expression-invalid, …). Machine-readable and stable across releases — the key a renderer groups or suppresses by.
pathstring✅Config path inside the SUBMITTED body (flows[0].nodes[1].config.multi), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. The TOP-LEVEL collection entry follows one rule. The gate evaluates against a private per-write snapshot whose indexes no caller can resolve; a collection-resident write type is one whose collection that snapshot fills with the tenant's other stored items beside the written one, and there the entry is keyed by NAME, never by an array index — for example objects.acme_invoice.sharingModel (an object write) or datasets.acme_invoice_metrics.dimensions[0].field (a dataset write). Every other write type is the sole member of its own collection in that snapshot, so its [0] IS the written item and stays positional (for example flows[0]...). Which write types are collection-resident is derived, never listed: @objectstack/lint's runtime gate computes the set (NAME_KEYED_STACK_KEYS) as the collections it carries as resolution context that a gated write type also lands in, so it changes with the gate, not with this text. An entry whose name cannot be spliced into a dotted path keeps its index. Nested positions inside one named item stay positional (objects.acme_invoice.indexes[1]): they index the author's own document.
wherestring✅Human-readable location — flow "leave_approval" · node "approve". Prose for a person; use path for anything mechanical.
messagestring✅What is wrong, in the rule author's own words.
hintstring✅How to fix it.
severityEnum<'error' | 'warning' | 'info'>✅How the gate treated this finding. error means the write was REFUSED (these appear on the 422, never on a 2xx); warning / info are advisory — the write succeeded and the finding is FYI.

SearchAllHit

Properties

PropertyTypeRequiredDescription
objectstring✅Name of the object the hit belongs to.
idstring✅ID of the matched record.
titlestring✅Display title for the hit, resolved in order: the object's titleFormat template → the declared primary-title pointer (nameField, ADR-0079; deprecated alias displayNameField still honored) → conventional name fields → first/last name → the record ID as a string.
snippetstringoptionalExcerpt cut around the first matched term in a searchable text column, ellipsized at both ends when truncated. ABSENT when no source column literally contains a term (e.g. a pinyin companion match) — absence is a correct answer, not a miss.
recordRecord<string, any>✅The matched record as the engine's find path returns it (row-level security applied, internal fields already stripped). Object-specific — no cross-object field shape is promised beyond "a record of the named object".

SearchAllPageHit

Properties

PropertyTypeRequiredDescription
kind'page'✅Discriminant, always the literal page — kept on the hit (despite the dedicated array) so a client merging record and page hits into one palette list still holds a self-describing element.
namestring✅Machine name of the page (page.name) — the address a client routes to; the same name the metadata read door serves the page under.
titlestring✅Display title: the page label resolved through the shared i18n label resolution (exact tag → base language → regional sibling → default → en → any), falling back to the machine name when no label resolves.
snippetstringoptionalExcerpt cut around the first matched term in the page description, ellipsized at both ends when truncated — the same geometry as a record hit's snippet. ABSENT when the match is on the name or label only (the title already shows it) — absence is a correct answer, not a miss.
pageTypestringoptionalThe page's own declared type (record | app | home | …) when the served document carries one — a routing hint, deliberately typed as a plain string so a future page type cannot invalidate an already-produced search response.

SearchAllResponse

Properties

PropertyTypeRequiredDescription
querystring✅The TRIMMED query text the sweep ran with — empty string when the request carried none (the no-scan short-circuit).
hits{ object: string; id: string; title: string; snippet?: string; … }[]✅Matched records across objects, in scan order, capped at the overall limit (default 20, max 100) with at most perObject (default 5, max 25) per object.
pages{ kind: 'page'; name: string; title: string; snippet?: string; … }[]✅Published pages whose name, label, or description matches every term (each term may match in a different field; matching folds case), in the served listing's order, capped at perObject — the page store is swept as one more container, and more matching pages may exist beyond the cap. Produced ONLY on an unscoped sweep: a request that names objects asks for records of those objects and answers pages: []. The swept set is exactly what the caller's metadata read door (GET /api/v1/meta/page) serves — published state only, never drafts.
totalObjectsnumber✅Number of objects the sweep actually SCANNED (searchable, API-enabled, with a resolvable search-field set) — not the number of objects with hits. An object whose table was never provisioned is skipped and not counted.
totalHitsnumber✅Number of hits returned — equals hits.length. NOT a deployment-wide total-match count: matches beyond limit / perObject are not counted.
truncatedboolean✅True when the sweep stopped at the overall limit — more matches may exist beyond the returned set.

Nested Shape: SearchAllResponse.hits[number]

PropertyTypeRequiredDescription
objectstring✅Name of the object the hit belongs to.
idstring✅ID of the matched record.
titlestring✅Display title for the hit, resolved in order: the object's titleFormat template → the declared primary-title pointer (nameField, ADR-0079; deprecated alias displayNameField still honored) → conventional name fields → first/last name → the record ID as a string.
snippetstringoptionalExcerpt cut around the first matched term in a searchable text column, ellipsized at both ends when truncated. ABSENT when no source column literally contains a term (e.g. a pinyin companion match) — absence is a correct answer, not a miss.
recordRecord<string, any>✅The matched record as the engine's find path returns it (row-level security applied, internal fields already stripped). Object-specific — no cross-object field shape is promised beyond "a record of the named object".

Nested Shape: SearchAllResponse.pages[number]

PropertyTypeRequiredDescription
kind'page'✅Discriminant, always the literal page — kept on the hit (despite the dedicated array) so a client merging record and page hits into one palette list still holds a self-describing element.
namestring✅Machine name of the page (page.name) — the address a client routes to; the same name the metadata read door serves the page under.
titlestring✅Display title: the page label resolved through the shared i18n label resolution (exact tag → base language → regional sibling → default → en → any), falling back to the machine name when no label resolves.
snippetstringoptionalExcerpt cut around the first matched term in the page description, ellipsized at both ends when truncated — the same geometry as a record hit's snippet. ABSENT when the match is on the name or label only (the title already shows it) — absence is a correct answer, not a miss.
pageTypestringoptionalThe page's own declared type (record | app | home | …) when the served document carries one — a routing hint, deliberately typed as a plain string so a future page type cannot invalidate an already-produced search response.

SetPresenceRequest

Properties

PropertyTypeRequiredDescription
channelstring✅Channel to set presence in
state{ userId: string; status: Enum<'online' | 'away' | 'busy' | 'offline'>; lastSeen: string; metadata?: Record<string, any> }✅Presence state to set

Nested Shape: SetPresenceRequest.state

PropertyTypeRequiredDescription
userIdstring✅User identifier
statusEnum<'online' | 'away' | 'busy' | 'offline'>✅Current presence status
lastSeenstring✅ISO 8601 datetime of last activity
metadataRecord<string, any>optionalCustom presence data (e.g., current page, custom status)

SetPresenceResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether presence was set

UninstallPackageRequest

Uninstall package request

Properties

PropertyTypeRequiredDescription
idstring✅Package ID to uninstall

UninstallPackageResponse

Uninstall package response

Properties

PropertyTypeRequiredDescription
idstring✅Uninstalled package ID
successboolean✅Whether uninstall succeeded
messagestringoptionalUninstall status message

UnregisterDeviceRequest

Properties

PropertyTypeRequiredDescription
deviceIdstring✅Device ID to unregister

UnregisterDeviceResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Whether unregistration succeeded

UpdateAiConversationRequest

Properties

PropertyTypeRequiredDescription
titlestringoptionalNew title
metadataRecord<string, any>optionalNew metadata

UpdateDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
idstring✅The ID of the record to update.
dataRecord<string, any>✅The fields to update (partial update).
expectedVersionstringoptionalOptimistic concurrency token (typically the updated_at value the client read). When provided, the server compares it against the current record version and returns 409 CONCURRENT_UPDATE if they differ. Optional — omit to skip the check. The quoted-empty entity-tag ("") is refused 400 VALIDATION_FAILED, not treated as omitted.

UpdateDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅Object name
idstring✅Updated record ID
recordRecord<string, any>✅Updated record
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: caller-supplied fields the engine LEGALLY stripped from the write before persisting — static readonly or a TRUE readonlyWhen predicate. Present ONLY when ≥1 field was dropped; the update still succeeded without them (status/success semantics unchanged — stripping is legitimate, not an error). REST additionally surfaces this as the X-ObjectStack-Dropped-Fields response header. Optional — omit-when-empty keeps the shape backward-compatible for existing clients that only read record.
warnings{ field: string; code: string; message: string; rule?: string; … }[]optionalThe advisory validation-rule hits of THIS write: each severity: 'warning' or 'info' rule whose verdict on the written record was "violated", with its rule name, severity, field, code and author-written message — the same element the validate-only preview reports in a row's warnings. Advisory rules never block, so the write succeeded; this is the advisory to show the person who made it. Present ONLY when at least one advisory rule hit; a write with no hit omits the key. Hits of a nested write a hook or a flow made inside this one are not included, and a rule that could not be evaluated is not a hit.

Nested Shape: UpdateDataResponse.droppedFields[number]

A write-path strip event: caller-supplied fields legally dropped from the payload

PropertyTypeRequiredDescription
objectstring✅Object the write targeted (resolved object name)
fieldsstring[]✅Caller-supplied field names the engine removed from the write payload
reasonEnum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'>✅Why the fields were dropped: static readonly, a TRUE readonlyWhen predicate, the primary-key strip of a payload id the engine ruled is not an identifier, or a computed (formula) field no driver has a column for

Nested Shape: UpdateDataResponse.warnings[number]

PropertyTypeRequiredDescription
fieldstring✅The field the finding is about (_record for an object-level rule).
codestring✅Machine-readable finding code, e.g. required, invalid_type, rule_violation.
messagestring✅Human-readable message — a validation rule's author-written text where one exists.
rulestringoptionalThe name of the ADVISORY validation rule this finding reports. Present only on an advisory rule hit.
severityEnum<'warning' | 'info'>optionalThe advisory rule's declared severity, warning or info. Present only on an advisory rule hit.

UpdateManyDataRequest

Properties

PropertyTypeRequiredDescription
records{ id: string; data: Record<string, any> }[]✅Array of records to update (server caps the count — see batch.maxBatchSize)
options{ atomic?: boolean; returnRecords?: boolean; continueOnError?: boolean }optionalUpdate options
objectstring✅Object name

Nested Shape: UpdateManyDataRequest.records[number]

PropertyTypeRequiredDescription
idstring✅Record ID
dataRecord<string, any>✅Fields to update

Nested Shape: UpdateManyDataRequest.options

PropertyTypeRequiredDescription
atomicbooleanoptional (default: false)Opt-in all-or-nothing. When explicitly true the whole batch runs inside ONE engine transaction: the first failure rolls back every prior write, and the response reports zero successes — each row carries errors[0].code ROLLED_BACK (written, then undone), the causal row its own error, and rows never reached NOT_ATTEMPTED. A runtime that cannot roll back REFUSES the request (501 NOT_IMPLEMENTED) rather than silently degrading to best-effort — probe capabilities.transactionalBatch on /discovery first. Takes precedence over continueOnError. Default false: sequential best-effort.
returnRecordsbooleanoptional (default: false)If true, return full record data in response
continueOnErrorbooleanoptional (default: false)If true (and atomic=false), continue processing remaining records after errors. Default false: the first failure ENDS the run — records before it stay written (nothing is rolled back on this arm), and every record after it is reported errors[0].code NOT_ATTEMPTED rather than omitted, so results always covers all total records and succeeded + failed === total.
validateOnlyneveroptional[REMOVED] options.validateOnly was removed from BatchOptions in @objectstack/spec 17.0.0. It was never implemented: the batch surfaces persisted regardless, so a "dry-run" would have silently executed. There is no dry-run today — drop the key. If you need to preview a batch without writing, open an issue so it can be designed (no-commit cascade / constraint semantics) and reintroduced as a flag that actually holds.

UpdateManyDataResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
operationEnum<'create' | 'update' | 'upsert' | 'delete'>optionalOperation type that was performed
totalnumber✅Total number of records in the batch
succeedednumber✅Number of records that succeeded
failednumber✅Number of records that failed
results{ id?: string; success: boolean; errors?: object[]; data?: Record<string, any>; … }[]✅Detailed results for each record

Nested Shape: UpdateManyDataResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: UpdateManyDataResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: UpdateManyDataResponse.results[number]

PropertyTypeRequiredDescription
idstringoptionalRecord ID if operation succeeded
successboolean✅Whether this record was processed successfully
errors{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }[]optionalArray of errors if operation failed. Branch on errors[0].code — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error. A NON-atomic batch that stopped (the continueOnError: false default) marks its un-attempted tail with the same NOT_ATTEMPTED code — rows before the failure stay written and keep reporting success, since nothing was rolled back.
dataRecord<string, any>optionalFull record data (if returnRecords=true)
indexnumberoptionalIndex of the record in the request array
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability: caller-supplied fields LEGALLY stripped from THIS row before it was written — static readonly / TRUE readonlyWhen on update, or the in-engine static readonly strip on create. Per-row because a batch can drop different fields on different rows (readonlyWhen is record-state-dependent). Present ONLY when ≥1 field was dropped for this row; the row still succeeded (success unchanged). A single response header cannot express per-row drops, so this body field is the canonical bulk channel — REST does not emit X-ObjectStack-Dropped-Fields for batches. Optional — omit-when-empty keeps the shape backward-compatible.

UpdateNotificationPreferencesRequest

Properties

PropertyTypeRequiredDescription
preferences{ email?: boolean; push?: boolean; inApp?: boolean; digest?: Enum<'none' | 'daily' | 'weekly'>; … }✅Preferences to update

Nested Shape: UpdateNotificationPreferencesRequest.preferences

PropertyTypeRequiredDescription
emailbooleanoptional (default: true)Receive email notifications
pushbooleanoptional (default: true)Receive push notifications
inAppbooleanoptional (default: true)Receive in-app notifications
digestEnum<'none' | 'daily' | 'weekly'>optional (default: "none")Email digest frequency
channelsRecord<string, { enabled?: boolean; email?: boolean; push?: boolean }>optionalPer-channel notification preferences

UpdateNotificationPreferencesResponse

Properties

PropertyTypeRequiredDescription
preferences{ email?: boolean; push?: boolean; inApp?: boolean; digest?: Enum<'none' | 'daily' | 'weekly'>; … }✅Updated notification preferences

Nested Shape: UpdateNotificationPreferencesResponse.preferences

PropertyTypeRequiredDescription
emailbooleanoptional (default: true)Receive email notifications
pushbooleanoptional (default: true)Receive push notifications
inAppbooleanoptional (default: true)Receive in-app notifications
digestEnum<'none' | 'daily' | 'weekly'>optional (default: "none")Email digest frequency
channelsRecord<string, { enabled?: boolean; email?: boolean; push?: boolean }>optionalPer-channel notification preferences

ValidateDataIssue

Properties

PropertyTypeRequiredDescription
fieldstring✅The field the finding is about (_record for an object-level rule).
codestring✅Machine-readable finding code, e.g. required, invalid_type, rule_violation.
messagestring✅Human-readable message — a validation rule's author-written text where one exists.
rulestringoptionalThe name of the ADVISORY validation rule this finding reports. Present only on an advisory rule hit.
severityEnum<'warning' | 'info'>optionalThe advisory rule's declared severity, warning or info. Present only on an advisory rule hit.

ValidateDataRequest

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
dataRecord<string, any> | Record<string, any>[]✅A candidate record, or an array of them. Nothing is persisted.
modeEnum<'insert' | 'update'>optionalWhich write the verdict should predict. insert (default) walks every declared field, so a missing required field is a finding; update checks only the supplied keys, matching a PATCH. An update row that carries its id is judged as the by-id update judges it: its rules read the stored row that id names, merged with the supplied keys. The stored row is read with the caller's own read access, so a row the caller cannot read is judged on the supplied keys alone, as a row with no id is.

ValidateDataResponse

Properties

PropertyTypeRequiredDescription
objectstring✅The object name.
modeEnum<'insert' | 'update'>✅The write mode the verdict was reached for.
validboolean✅True when EVERY row is valid — the whole-set answer.
results{ valid: boolean; errors: object[]; warnings: object[]; droppedFields?: object[] }[]✅Per-row verdicts, in submission order.
posture{ valueShapeStrict: boolean; mediaValueShapeStrict: boolean }✅The ADR-0104 posture the verdict was reached under — reported because it is the difference between "this row is fine" and "this row is fine HERE". The same row can be an error on a self-certified deployment and an admitted warning on an un-migrated one, and a caller explaining a verdict needs to know which it got. An unconditionally-strict preview was considered and rejected (option B): it would fail rows on every un-migrated deployment that the write would have accepted.

Nested Shape: ValidateDataResponse.results[number]

PropertyTypeRequiredDescription
validboolean✅True when this row would be accepted by the write path.
errors{ field: string; code: string; message: string; rule?: string; … }[]✅Findings that would REJECT this row. Empty when valid.
warnings{ field: string; code: string; message: string; rule?: string; … }[]✅Findings the target deployment ADMITS rather than rejects; the row is valid, and the write would store it. Two populations, in this order: ADR-0104 value shapes admitted under a warn-first posture (the write logs the same complaint), then the hits of ADVISORY validation rules (severity: 'warning' or 'info'), each naming its rule and severity — the same elements the write answers as its own warnings. An advisory hit is reported only on a row the verdict accepts, as on the write, which answers a refused row with its error.
droppedFields{ object: string; fields: string[]; reason: Enum<'readonly' | 'readonly_when' | 'primary_key' | 'computed'> }[]optionalWrite-observability for the preview: caller-supplied fields the write would LEGALLY strip from THIS row, one event per reason, in the shape and reason vocabulary the write itself reports. A strip is not a finding: valid is unaffected, and the fields appear in neither errors nor warnings. Present only when at least one field would be dropped. A server that does not produce this report omits the key too, so an absent key alone does not prove nothing would be dropped.

Nested Shape: ValidateDataResponse.posture

PropertyTypeRequiredDescription
valueShapeStrictboolean✅True when this deployment rejects non-conforming value shapes (ADR-0104 self-certified).
mediaValueShapeStrictboolean✅The same, for media field value shapes.

On this page

TypeScript UsageAiAgentCapabilitiesPropertiesAiAgentChatRequestPropertiesNested Shape: AiAgentChatRequest.messages[number]AiAgentSummaryPropertiesNested Shape: AiAgentSummary.capabilitiesAiAgentsResponsePropertiesNested Shape: AiAgentsResponse.agents[number]AiChatRequestPropertiesNested Shape: AiChatRequest.messages[number]AiChatResponsePropertiesNested Shape: AiChatResponse.usageAiCompleteRequestPropertiesAiConversationPropertiesNested Shape: AiConversation.messages[number]AiMessagePropertiesAiModelsResponsePropertiesNested Shape: AiModelsResponse.models[number]AiPendingActionPropertiesAiPendingActionStatusAllowed ValuesAiStreamChunkPropertiesApproveAiPendingActionResponsePropertiesAuditMetaItemRequestPropertiesAuditMetaItemResponsePropertiesNested Shape: AuditMetaItemResponse.events[number]AutomationActionsResponsePropertiesNested Shape: AutomationActionsResponse.actions[number]AutomationTriggerRequestPropertiesAutomationTriggerResponsePropertiesBatchDataRequestPropertiesNested Shape: BatchDataRequest.requestBatchDataResponsePropertiesNested Shape: BatchDataResponse.errorNested Shape: BatchDataResponse.metaNested Shape: BatchDataResponse.results[number]CheckPermissionRequestPropertiesCheckPermissionResponsePropertiesCloneDataResponsePropertiesNested Shape: CloneDataResponse.droppedFields[number]Nested Shape: CloneDataResponse.warnings[number]CreateAiConversationRequestPropertiesCreateDataRequestPropertiesCreateDataResponsePropertiesNested Shape: CreateDataResponse.droppedFields[number]Nested Shape: CreateDataResponse.warnings[number]CreateManyDataRequestPropertiesCreateManyDataResponsePropertiesNested Shape: CreateManyDataResponse.droppedFields[number]DeleteDataRequestPropertiesDeleteDataResponsePropertiesDeleteManyDataRequestPropertiesNested Shape: DeleteManyDataRequest.optionsDeleteManyDataResponsePropertiesNested Shape: DeleteManyDataResponse.errorNested Shape: DeleteManyDataResponse.metaNested Shape: DeleteManyDataResponse.results[number]DeleteMetaItemRequestPropertiesDeleteMetaItemResponsePropertiesNested Shape: DeleteMetaItemResponse.projectionAppliedDiffMetaItemResponsePropertiesNested Shape: DiffMetaItemResponse.added[number]Nested Shape: DiffMetaItemResponse.removed[number]Nested Shape: DiffMetaItemResponse.changed[number]DisablePackageRequestPropertiesDisablePackageResponsePropertiesNested Shape: DisablePackageResponse.packageEnablePackageRequestPropertiesEnablePackageResponsePropertiesNested Shape: EnablePackageResponse.packageFindDataRequestPropertiesNested Shape: FindDataRequest.queryFindDataResponsePropertiesFindReferencesToMetaResponsePropertiesNested Shape: FindReferencesToMetaResponse.references[number]FlowRegistrationUnion OptionsOption 1PropertiesOption 2PropertiesNested Shape: FlowRegistration[state='refused'].reasonOption 3PropertiesFlowRegistrationRefusalPropertiesGetDataRequestPropertiesGetDataResponsePropertiesGetDiscoveryRequestPropertiesGetDiscoveryResponsePropertiesNested Shape: GetDiscoveryResponse.routesNested Shape: GetDiscoveryResponse.services[string]Nested Shape: GetDiscoveryResponse.capabilitiesNested Shape: GetDiscoveryResponse.schemaDiscoveryNested Shape: GetDiscoveryResponse.scopingNested Shape: GetDiscoveryResponse.authFamiliesGetEffectivePermissionsRequestPropertiesGetEffectivePermissionsResponsePropertiesNested Shape: GetEffectivePermissionsResponse.objects[string]GetFieldLabelsRequestPropertiesGetFieldLabelsResponsePropertiesNested Shape: GetFieldLabelsResponse.labels[string]GetLocalesRequestPropertiesGetLocalesResponsePropertiesNested Shape: GetLocalesResponse.locales[number]GetMetaDiagnosticsResponsePropertiesNested Shape: GetMetaDiagnosticsResponse.entries[number]Nested Shape: GetMetaDiagnosticsResponse.stats[string]GetMetaItemCachedRequestPropertiesNested Shape: GetMetaItemCachedRequest.cacheRequestGetMetaItemCachedResponsePropertiesNested Shape: GetMetaItemCachedResponse.etagNested Shape: GetMetaItemCachedResponse.cacheControlNested Shape: GetMetaItemCachedResponse.registration[state='registered']Nested Shape: GetMetaItemCachedResponse.registration[state='refused']Nested Shape: GetMetaItemCachedResponse.registration[state='unregistered']GetMetaItemLayeredRequestPropertiesGetMetaItemLayeredResponsePropertiesNested Shape: GetMetaItemLayeredResponse._diagnosticsGetMetaItemRequestPropertiesGetMetaItemResponsePropertiesNested Shape: GetMetaItemResponse.sortabilityNested Shape: GetMetaItemResponse.registration[state='registered']Nested Shape: GetMetaItemResponse.registration[state='refused']Nested Shape: GetMetaItemResponse.registration[state='unregistered']GetMetaItemsRequestPropertiesGetMetaItemsResponsePropertiesNested Shape: GetMetaItemsResponse.registrations[string][state='registered']Nested Shape: GetMetaItemsResponse.registrations[string][state='refused']Nested Shape: GetMetaItemsResponse.registrations[string][state='unregistered']GetMetaTypesRequestPropertiesGetMetaTypesResponsePropertiesNested Shape: GetMetaTypesResponse.entries[number]GetNotificationPreferencesRequestPropertiesGetNotificationPreferencesResponsePropertiesNested Shape: GetNotificationPreferencesResponse.preferencesGetObjectPermissionsRequestPropertiesGetObjectPermissionsResponsePropertiesNested Shape: GetObjectPermissionsResponse.permissionsNested Shape: GetObjectPermissionsResponse.fieldPermissions[string]GetPackageRequestPropertiesGetPackageResponsePropertiesNested Shape: GetPackageResponse.packageGetPresenceRequestPropertiesGetPresenceResponsePropertiesNested Shape: GetPresenceResponse.members[number]GetPublishedMetaItemResponseGetTranslationsRequestPropertiesGetTranslationsResponsePropertiesNested Shape: GetTranslationsResponse.translationsGetUiViewRequestPropertiesGetUiViewResponsePropertiesNested Shape: GetUiViewResponse.listNested Shape: GetUiViewResponse.formNested Shape: GetUiViewResponse.listViews[string]Nested Shape: GetUiViewResponse.formViews[string]Nested Shape: GetUiViewResponse.protectionHistoryMetaItemRequestPropertiesHistoryMetaItemResponsePropertiesNested Shape: HistoryMetaItemResponse.events[number]HttpFindQueryParamsPropertiesInstallPackageRequestPropertiesNested Shape: InstallPackageRequest.manifestInstallPackageResponsePropertiesNested Shape: InstallPackageResponse.packageNested Shape: InstallPackageResponse.dependencyResolutionListAiConversationsRequestPropertiesListAiConversationsResponsePropertiesNested Shape: ListAiConversationsResponse.conversations[number]ListAiPendingActionsRequestPropertiesListAiPendingActionsResponsePropertiesNested Shape: ListAiPendingActionsResponse.items[number]ListDraftsResponsePropertiesNested Shape: ListDraftsResponse.drafts[number]ListNotificationsRequestPropertiesListNotificationsResponsePropertiesNested Shape: ListNotificationsResponse.notifications[number]ListPackagesRequestPropertiesListPackagesResponsePropertiesNested Shape: ListPackagesResponse.packages[number]MarkAllNotificationsReadRequestPropertiesMarkAllNotificationsReadResponsePropertiesMarkNotificationsReadRequestPropertiesMarkNotificationsReadResponsePropertiesMetadataConflictErrorPropertiesNotificationPropertiesNotificationPreferencesPropertiesNested Shape: NotificationPreferences.channels[string]PublishMetaItemRequestPropertiesPublishMetaItemResponsePropertiesNested Shape: PublishMetaItemResponse.seedAppliedNested Shape: PublishMetaItemResponse.materializeAppliedNested Shape: PublishMetaItemResponse.projectionAppliedNested Shape: PublishMetaItemResponse.advisories[number]PublishPackageDraftsResponsePropertiesNested Shape: PublishPackageDraftsResponse.published[number]Nested Shape: PublishPackageDraftsResponse.failed[number]Nested Shape: PublishPackageDraftsResponse.seedAppliedNested Shape: PublishPackageDraftsResponse.materializeAppliedRealtimeConnectRequestPropertiesRealtimeConnectResponsePropertiesRealtimeDisconnectRequestPropertiesRealtimeDisconnectResponsePropertiesRealtimeSubscribeRequestPropertiesRealtimeSubscribeResponsePropertiesRealtimeUnsubscribeRequestPropertiesRealtimeUnsubscribeResponsePropertiesRegisterDeviceRequestPropertiesRegisterDeviceResponsePropertiesRejectAiPendingActionResponsePropertiesRollbackMetaItemResponsePropertiesRuntimeAuthoringIssuePropertiesSaveMetaItemRequestPropertiesSaveMetaItemResponsePropertiesNested Shape: SaveMetaItemResponse.projectionAppliedNested Shape: SaveMetaItemResponse.advisories[number]SearchAllHitPropertiesSearchAllPageHitPropertiesSearchAllResponsePropertiesNested Shape: SearchAllResponse.hits[number]Nested Shape: SearchAllResponse.pages[number]SetPresenceRequestPropertiesNested Shape: SetPresenceRequest.stateSetPresenceResponsePropertiesUninstallPackageRequestPropertiesUninstallPackageResponsePropertiesUnregisterDeviceRequestPropertiesUnregisterDeviceResponsePropertiesUpdateAiConversationRequestPropertiesUpdateDataRequestPropertiesUpdateDataResponsePropertiesNested Shape: UpdateDataResponse.droppedFields[number]Nested Shape: UpdateDataResponse.warnings[number]UpdateManyDataRequestPropertiesNested Shape: UpdateManyDataRequest.records[number]Nested Shape: UpdateManyDataRequest.optionsUpdateManyDataResponsePropertiesNested Shape: UpdateManyDataResponse.errorNested Shape: UpdateManyDataResponse.metaNested Shape: UpdateManyDataResponse.results[number]UpdateNotificationPreferencesRequestPropertiesNested Shape: UpdateNotificationPreferencesRequest.preferencesUpdateNotificationPreferencesResponsePropertiesNested Shape: UpdateNotificationPreferencesResponse.preferencesValidateDataIssuePropertiesValidateDataRequestPropertiesValidateDataResponsePropertiesNested Shape: ValidateDataResponse.results[number]Nested Shape: ValidateDataResponse.posture