ObjectStackObjectStack

Connector — Integration Protocol reference

Defines the standard connector specification for external system integration.

Connector Protocol - LEVEL 3: Enterprise Connector

Defines the standard connector specification for external system integration. Connectors enable ObjectStack to call SaaS apps, databases, file storage and message queues through a unified protocol.

Positioning in the sync/integration layering — this file is now the ONLY layer. Both layers above it were retired under ADR-0049 for the same measured reason, that no engine ever executed them: L1 "Simple Sync" (automation/sync.zod.ts) in #4738, and L2 "ETL Pipeline" (automation/etl.zod.ts) in #6414. See packages/spec/docs/SYNC_ARCHITECTURE.md:

  • Enterprise Connector (THIS FILE) - System integrators - Full SAP integration

SCOPE: Most comprehensive integration layer. Includes authentication, actions, retry policies and lifecycle management.

This protocol supports multiple authentication strategies and an executed retry policy. It declares no health probe, no circuit breaker, no authored status, no webhooks, no triggers and no sync of its own — see "What this layer does NOT provide" below.

What this layer does NOT provide

There is no outbound rate limiting. This header used to advertise "rate limiting" twice — once in the SCOPE line, once as "comprehensive rate limiting" — and no engine ever backed either. connector.rateLimitConfig, and the entire ConnectorRateLimitConfig / RateLimitStrategy shape behind it, was removed in @objectstack/spec 17.0.0 (#4911, ADR-0049 D2), because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime security/rate-limit.ts) throttles INBOUND requests to us; nothing throttles the calls a connector makes out. Do not substitute shared's RateLimitConfig — that is the inbound limiter and would cap the wrong direction. Until an outbound throttle exists, rate-limit at the connector provider or upstream gateway.

What you MAY reach for is retryConfig — ADR-0049 ruled 实现 and the platform now executes it. A connector's declared policy is applied at the one place the platform makes an outbound call, shared/resilientFetch, via the single mapping in integration/connector-fetch-policy.ts: the backoff shape (strategy, initialDelayMs, backoffMultiplier, maxDelayMs, jitter), the attempt count (maxAttempts, which counts TOTAL calls with the first included), what is retried (retryableStatusCodes, whose default [408, 429, 500, 502, 503, 504] includes 429, and retryOnNetworkError), and requestTimeoutMs as each attempt's deadline. It reaches a provider factory through ConnectorProviderContext, so a custom provider doing its own I/O honours the same policy the built-in HTTP providers honour by construction. ⚠️ Retrying a 429 is not throttling it: a retry policy spaces out the calls you already made, it does not cap the rate, so the sentence above about rate limiting stands unchanged.

There is no connector health probe and no circuit breaker. The health block (healthCheck and circuitBreaker) was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) together with the authored status and the nested webhooks array: nothing ever scheduled a probe, opened a breaker, read an authored status or delivered a webhook declared inside a connector. Implement probes and circuit breaking in the connector provider or an upstream gateway; whether a registered connector can be dispatched is the computed state (ready / degraded) that GET /api/v1/automation/connectors reports; and a webhook that is actually delivered is declared in the stack's top-level webhooks: collection. The "REMOVED: health, status and the nested webhooks" section below records the measurement.

There are no connector triggers. The triggers array (polling / webhook) was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove): nothing ever registered, polled or received one, so a declared trigger never started a flow. Work starts from a FLOW that calls the connector's action in a connector_action node — an api flow for an external event, a schedule flow for a scheduled pull. The "REMOVED: triggers" section below records the measurement.

connectionTimeoutMs used to be the second exception and is now removed (ADR-0049, the narrower second decision that surface was owed): it was carried to a provider factory but never applied as a deadline anywhere, and it is not implementable where it was declared, because a WHATWG fetch exposes one AbortSignal over the whole operation and never the connection phase alone. requestTimeoutMs is the bound the platform can keep. The full removal reasoning is recorded at each removal site: the "REMOVED: connectionTimeoutMs" and "REMOVED: outbound rate limiting" blocks in integration/connector.zod.ts, and packages/spec/docs/SYNC_ARCHITECTURE.md.

There is no connector-attached sync and no connector field mapping. syncConfig and fieldMappings were removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove): no engine ever ran a sync or moved a value through a connector field mapping. A sync is defined on its TARGET — a mapping (data/mapping.zod.ts) whose connectorSource names the connector it pulls from, with a job for the cadence; per-field value conversion is that mapping's fieldMapping[].transform, applied row by row by the REST import path. The "REMOVED: connector-attached sync" section below records the measurement.

Runtime contract — descriptor vs. registered connector (#2612)

This schema serves TWO distinct consumers; do not conflate them:

  1. Runtime registration (plugin-only). The automation engine's connector registry — what GET /connectors lists and the connector_action flow node dispatches — is populated exclusively by plugins calling engine.registerConnector(def, handlers) with a handler per declared action (ADR-0018 §Addendum). The definition is validated against this schema at registration.
  2. Declarative connectors: stack entries (catalog descriptors). Stack metadata validated against this schema is registered as kind 'connector' for discovery/documentation/marketplace purposes only — it never reaches the runtime registry, because an action here carries no execution binding (deliberately: ADR-0023 rejected re-inventing OpenAPI inside this schema). The automation service warns at boot about declared entries with actions that lack a same-name runtime registration; mark deliberate catalog-only entries with enabled: false. Provider-bound declarative instances that a generic executor (connector-openapi / connector-mcp) materializes at boot are tracked in #2977 (ADR-0097).

Authentication is now imported from the canonical auth/config.zod.ts.

When to Use This Layer

Use Enterprise Connector when:

  • Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle)
  • Complex OAuth2/SAML authentication required
  • Full CRUD operations through the connector's actions
  • Need comprehensive retry strategies and error handling

Examples:

  • Full Salesforce integration
  • SAP ERP connector with CDC (Change Data Capture)
  • Microsoft Dynamics 365 connector

When to downgrade:

  • Per-field value conversion on import only → the import mapping's own transform (data/mapping.zod.ts), which the REST import path executes row by row. (This used to point at automation/etl.zod.ts; L2 was retired at #6414 for having no executor, so the pointer would have been a signpost landing nowhere — the same defect class this header names below.)

There is no "Trigger Registry" alternative

This header used to carry a "When to use Integration Connector vs. Trigger Registry?" comparison, steering "lightweight" cases to automation/trigger-registry.zod.ts. That file was a third declaration of the connector vocabulary with zero consumers — nothing registered, validated or executed against it — so the guidance pointed authors, with the platform's authority, at a dead end (#4499; removed alongside the #4480 per-provider template cluster). The same defect class as the capabilities.readOnly prescription #4487 corrected: a signpost must land somewhere enforced. Lightweight cases are served HERE — a connector instance with simple auth. (Both automation-side layers were themselves retired as dead ends of the same class: L1 "Simple Sync" in #4738, L2 etl.zod.ts in #6414. This paragraph named L2 as the transformation destination until the second retirement; a signpost that must land somewhere enforced cannot make an exception for itself.)

Source: packages/spec/src/integration/connector.zod.ts

TypeScript Usage

import { ConnectorSchema, ConnectorActionSchema, ConnectorActionEffectSchema, ConnectorInstanceAPIKeyAuthSchema, ConnectorInstanceAuthSchema, ConnectorInstanceBasicAuthSchema, ConnectorInstanceBearerAuthSchema, ConnectorInstanceNoAuthSchema, ConnectorRetryStrategySchema, ConnectorTypeSchema, DeclarativeConnectorEntrySchema, RetryConfigSchema } from '@objectstack/spec/integration';
import type { Connector, ConnectorAction, ConnectorActionEffect, ConnectorInstanceAPIKeyAuth, ConnectorInstanceAuth, ConnectorInstanceBasicAuth, ConnectorInstanceBearerAuth, ConnectorInstanceNoAuth, ConnectorRetryStrategy, ConnectorType, DeclarativeConnectorEntry, RetryConfig } from '@objectstack/spec/integration';

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

Connector

Properties

PropertyTypeRequiredDescription
namestring✅Unique connector identifier
labelstring✅Display label
typeEnum<'saas' | 'database' | 'file_storage' | 'message_queue' | 'api' | 'custom'>✅Connector type
descriptionstringoptionalConnector description
iconstringoptionalIcon identifier
authentication{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } | { type: 'api-key'; key: string; headerName?: string; paramName?: string } | { type: 'basic'; username: string; password: string } | { type: 'bearer'; token: string } | { type: 'none' }optional (default: {"type":"none"})Authentication configuration (runtime shape with inline secrets — plugin-supplied at registerConnector). Authored entries must not inline secrets: use auth.credentialRef on a provider-bound instance.
providerstringoptionalGeneric-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097).
providerConfigRecord<string, any>optionalProvider-specific config validated by the provider factory at boot (e.g. { spec, baseUrl } for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires provider.
auth{ type: 'none' } | { type: 'bearer'; credentialRef: string } | { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } | { type: 'basic'; username: string; credentialRef: string }optionalDeclarative instance auth — references credentials via credentialRef (resolved at boot), never inline secrets. Requires provider (ADR-0097).
actions{ key: string; label: string; description?: string; inputSchema?: Record<string, any>; … }[]optional
triggersneveroptional[REMOVED] connector.triggers was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a connector trigger never started anything: AutomationEngine.registerConnector registers a connector's actions only, no polling loop read intervalSeconds (or the interval spelling it was renamed from), and no receiver was driven by a webhook trigger. Delete the key; the ConnectorTrigger shape leaves with it. To start work from an external system, write a flow that calls the connector's action in a connector_action node: for an external event, an api flow that the event's sender calls; for a scheduled pull, a schedule flow. 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.
syncConfigneveroptional[REMOVED] connector.syncConfig was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever ran a connector-attached sync: nothing read strategy, direction, realtimeSync, timestampField, conflictResolution, batchSize, deleteMode or filters, so the latest_wins and soft_delete defaults resolved and deleted nothing. Delete the key; the DataSyncConfig shape leaves with it. A sync is defined on its TARGET: a mapping (targetObject, fieldMapping, mode, upsertKey) whose connectorSource names the rest or openapi connector it pulls from, the read action and an optional timestamp watermark, with a job for the cadence. Its pull runs when a job drives it — a job whose pull: { mapping } names that mapping; the binding alone moves no rows. 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.
fieldMappingsneveroptional[REMOVED] connector.fieldMappings was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever moved a value through a connector field mapping: nothing read source, target, defaultValue, dataType, required or syncMode. Delete the key; the ConnectorFieldMapping shape leaves with it. Map fields on the sync's TARGET instead: a mapping's fieldMapping (source → target, with a transform the import path executes), which its connectorSource pulls through. 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.
webhooksneveroptional[REMOVED] connector.webhooks was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a webhook item, so it was never materialized into sys_webhook and never delivered, and nothing emits the connector events its events list could name (sync.completed, auth.expired and the rest). Delete the key; the nested shape leaves with it (WebhookConfig, WebhookEvent, WebhookSignatureAlgorithm). To have a webhook actually sent, declare it in the stack's top-level webhooks: collection, which is materialized into sys_webhook and delivered on record events — note that doing so STARTS deliveries this connector never made. 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.
rateLimitConfigneveroptional[REMOVED] connector.rateLimitConfig was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — the entire shape is gone, not just this key: ConnectorRateLimitConfig and its RateLimitStrategy enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime security/rate-limit.ts) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute shared RateLimitConfig — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. 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.
retryConfig{ strategy?: Enum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }optionalRetry configuration
connectionTimeoutMsneveroptional[REMOVED] connector.connectionTimeoutMs was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the platform never honoured it and cannot honour it where it was declared: a connector's outbound call is a WHATWG fetch, whose only cancellation surface is one AbortSignal over the whole operation, so nothing there observes the connection phase separately, and the value only ever travelled (onto the reported def and the materialization fingerprint) without ever bounding a connect. Delete the key. Use requestTimeoutMs for the deadline the platform does keep — it is applied as resilientFetch's per-attempt timeout — and bound the connect phase at a connector provider or upstream gateway on a transport that can separate the phases. 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.
requestTimeoutMsnumberoptional (default: 30000)Request timeout in ms
statusneveroptional[REMOVED] connector.status was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: status: 'active' neither enabled nor advertised a connector, and 'error' or 'configuring' changed nothing either. Delete the key; the ConnectorStatus enum leaves with it. On a declarative entry, enabled: false is what withdraws a materialized instance or marks a catalog-only descriptor, and whether a registered connector can be dispatched is computed by the runtime and reported as state (ready or degraded) on GET /api/v1/automation/connectors — no authored value sets it. 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.
enabledbooleanoptional (default: true)Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor.
errorMappingneveroptional[REMOVED] connector.errorMapping was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no provider, dispatcher or materializer mapped an external error through the rules, so unmappedBehavior configured nothing and a rule's userMessage was never shown to anyone (that spelling is the live API-error channel, ApiError.userMessage, which a thrown HTTP error declares — not connector metadata). Delete the key; the whole shape leaves with it (ErrorMappingConfig, ErrorMappingRule and the ConnectorErrorCategory enum). There is no replacement, because no error-mapping engine exists: a connector's failures reach callers as the provider's own errors (ADR-0097). 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.
healthneveroptional[REMOVED] connector.health was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no connector health probe or circuit breaker ever existed: nothing scheduled a healthCheck request, counted consecutive failures against a threshold, or opened, half-opened or closed a circuitBreaker, and no fallbackStrategy was ever applied, so every key in the block configured nothing. That includes circuitBreaker.monitoringWindowMs and the monitoringWindow spelling it was renamed from: the renamed key is removed with the rest. Delete the key; the whole shape leaves with it (ConnectorHealth, HealthCheckConfig, CircuitBreakerConfig). Whether a connector can be dispatched is computed, not authored: GET /api/v1/automation/connectors reports each connector's state (ready or degraded). Put health probes and circuit breaking in the connector provider or an upstream gateway. 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.
metadataRecord<string, any>optionalCustom connector metadata
_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: Connector.authentication[type='oauth2']

PropertyTypeRequiredDescription
type'oauth2'✅
authorizationUrlstring✅OAuth2 authorization endpoint
tokenUrlstring✅OAuth2 token endpoint
clientIdstring✅OAuth2 client ID
clientSecretstring✅OAuth2 client secret (typically from ENV)
scopesstring[]optionalRequested OAuth2 scopes
redirectUristringoptionalOAuth2 redirect URI
refreshTokenstringoptionalRefresh token for token renewal
tokenExpiryintegeroptionalToken expiry timestamp (Unix milliseconds)

Nested Shape: Connector.authentication[type='api-key']

PropertyTypeRequiredDescription
type'api-key'✅
keystring✅API key value
headerNamestringoptional (default: "X-API-Key")HTTP header name for API key
paramNamestringoptionalQuery parameter name (alternative to header)

Nested Shape: Connector.authentication[type='basic']

PropertyTypeRequiredDescription
type'basic'✅
usernamestring✅Username
passwordstring✅Password

Nested Shape: Connector.authentication[type='bearer']

PropertyTypeRequiredDescription
type'bearer'✅
tokenstring✅Bearer token

Nested Shape: Connector.auth[type='bearer']

PropertyTypeRequiredDescription
type'bearer'✅
credentialRefstring✅Secrets-layer reference (e.g. an env-var name in the open tier) resolved to the bearer token at materialization. Never an inline token.

Nested Shape: Connector.auth[type='api-key']

PropertyTypeRequiredDescription
type'api-key'✅
credentialRefstring✅Secrets-layer reference resolved to the API key at materialization. Never an inline key.
headerNamestringoptionalHTTP header carrying the key (default X-API-Key).
paramNamestringoptionalQuery parameter carrying the key (alternative to header).

Nested Shape: Connector.auth[type='basic']

PropertyTypeRequiredDescription
type'basic'✅
usernamestring✅Username (not a secret; safe to keep in metadata).
credentialRefstring✅Secrets-layer reference resolved to the password at materialization. Never an inline password.

Nested Shape: Connector.actions[number]

PropertyTypeRequiredDescription
keystring✅Action key (machine name)
labelstring✅Human readable label
descriptionstringoptional
inputSchemaRecord<string, any>optionalInput parameters schema (JSON Schema)
outputSchemaRecord<string, any>optionalOutput schema (JSON Schema)
effectEnum<'read' | 'write'>optionalWhat the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero

Nested Shape: Connector.retryConfig

PropertyTypeRequiredDescription
strategyEnum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'>optional (default: "exponential_backoff")Retry strategy
maxAttemptsnumberoptional (default: 3)Maximum retry attempts
initialDelayMsnumberoptional (default: 1000)Initial retry delay in ms
maxDelayMsnumberoptional (default: 60000)Maximum retry delay in ms
backoffMultipliernumberoptional (default: 2)Exponential backoff multiplier
retryableStatusCodesnumber[]optional (default: [408,429,500,502,503,504])HTTP status codes to retry
retryOnNetworkErrorbooleanoptional (default: true)Retry on network errors
jitterbooleanoptional (default: true)Add jitter to retry delays

ConnectorAction

Properties

PropertyTypeRequiredDescription
keystring✅Action key (machine name)
labelstring✅Human readable label
descriptionstringoptional
inputSchemaRecord<string, any>optionalInput parameters schema (JSON Schema)
outputSchemaRecord<string, any>optionalOutput schema (JSON Schema)
effectEnum<'read' | 'write'>optionalWhat the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero

ConnectorActionEffect

What the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero

Allowed Values

  • read
  • write

ConnectorInstanceAPIKeyAuth

Properties

PropertyTypeRequiredDescription
type'api-key'✅
credentialRefstring✅Secrets-layer reference resolved to the API key at materialization. Never an inline key.
headerNamestringoptionalHTTP header carrying the key (default X-API-Key).
paramNamestringoptionalQuery parameter carrying the key (alternative to header).

ConnectorInstanceAuth

Union Options

This schema accepts one of the following structures:

Option 1

Type: none

Properties

PropertyTypeRequiredDescription
type'none'✅

Option 2

Type: bearer

Properties

PropertyTypeRequiredDescription
type'bearer'✅
credentialRefstring✅Secrets-layer reference (e.g. an env-var name in the open tier) resolved to the bearer token at materialization. Never an inline token.

Option 3

Type: api-key

Properties

PropertyTypeRequiredDescription
type'api-key'✅
credentialRefstring✅Secrets-layer reference resolved to the API key at materialization. Never an inline key.
headerNamestringoptionalHTTP header carrying the key (default X-API-Key).
paramNamestringoptionalQuery parameter carrying the key (alternative to header).

Option 4

Type: basic

Properties

PropertyTypeRequiredDescription
type'basic'✅
usernamestring✅Username (not a secret; safe to keep in metadata).
credentialRefstring✅Secrets-layer reference resolved to the password at materialization. Never an inline password.


ConnectorInstanceBasicAuth

Properties

PropertyTypeRequiredDescription
type'basic'✅
usernamestring✅Username (not a secret; safe to keep in metadata).
credentialRefstring✅Secrets-layer reference resolved to the password at materialization. Never an inline password.

ConnectorInstanceBearerAuth

Properties

PropertyTypeRequiredDescription
type'bearer'✅
credentialRefstring✅Secrets-layer reference (e.g. an env-var name in the open tier) resolved to the bearer token at materialization. Never an inline token.

ConnectorInstanceNoAuth

Properties

PropertyTypeRequiredDescription
type'none'✅

ConnectorRetryStrategy

Retry strategy

Allowed Values

  • exponential_backoff
  • linear_backoff
  • fixed_delay
  • no_retry

ConnectorType

Connector type

Allowed Values

  • saas
  • database
  • file_storage
  • message_queue
  • api
  • custom

DeclarativeConnectorEntry

Properties

PropertyTypeRequiredDescription
namestring✅Unique connector identifier
labelstring✅Display label
typeEnum<'saas' | 'database' | 'file_storage' | 'message_queue' | 'api' | 'custom'>✅Connector type
descriptionstringoptionalConnector description
iconstringoptionalIcon identifier
authentication{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } | { type: 'api-key'; key: string; headerName?: string; paramName?: string } | { type: 'basic'; username: string; password: string } | { type: 'bearer'; token: string } | { type: 'none' }optional (default: {"type":"none"})Authentication configuration (runtime shape with inline secrets — plugin-supplied at registerConnector). Authored entries must not inline secrets: use auth.credentialRef on a provider-bound instance.
providerstringoptionalGeneric-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097).
providerConfigRecord<string, any>optionalProvider-specific config validated by the provider factory at boot (e.g. { spec, baseUrl } for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires provider.
auth{ type: 'none' } | { type: 'bearer'; credentialRef: string } | { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } | { type: 'basic'; username: string; credentialRef: string }optionalDeclarative instance auth — references credentials via credentialRef (resolved at boot), never inline secrets. Requires provider (ADR-0097).
actions{ key: string; label: string; description?: string; inputSchema?: Record<string, any>; … }[]optional
triggersneveroptional[REMOVED] connector.triggers was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a connector trigger never started anything: AutomationEngine.registerConnector registers a connector's actions only, no polling loop read intervalSeconds (or the interval spelling it was renamed from), and no receiver was driven by a webhook trigger. Delete the key; the ConnectorTrigger shape leaves with it. To start work from an external system, write a flow that calls the connector's action in a connector_action node: for an external event, an api flow that the event's sender calls; for a scheduled pull, a schedule flow. 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.
syncConfigneveroptional[REMOVED] connector.syncConfig was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever ran a connector-attached sync: nothing read strategy, direction, realtimeSync, timestampField, conflictResolution, batchSize, deleteMode or filters, so the latest_wins and soft_delete defaults resolved and deleted nothing. Delete the key; the DataSyncConfig shape leaves with it. A sync is defined on its TARGET: a mapping (targetObject, fieldMapping, mode, upsertKey) whose connectorSource names the rest or openapi connector it pulls from, the read action and an optional timestamp watermark, with a job for the cadence. Its pull runs when a job drives it — a job whose pull: { mapping } names that mapping; the binding alone moves no rows. 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.
fieldMappingsneveroptional[REMOVED] connector.fieldMappings was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever moved a value through a connector field mapping: nothing read source, target, defaultValue, dataType, required or syncMode. Delete the key; the ConnectorFieldMapping shape leaves with it. Map fields on the sync's TARGET instead: a mapping's fieldMapping (source → target, with a transform the import path executes), which its connectorSource pulls through. 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.
webhooksneveroptional[REMOVED] connector.webhooks was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a webhook item, so it was never materialized into sys_webhook and never delivered, and nothing emits the connector events its events list could name (sync.completed, auth.expired and the rest). Delete the key; the nested shape leaves with it (WebhookConfig, WebhookEvent, WebhookSignatureAlgorithm). To have a webhook actually sent, declare it in the stack's top-level webhooks: collection, which is materialized into sys_webhook and delivered on record events — note that doing so STARTS deliveries this connector never made. 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.
rateLimitConfigneveroptional[REMOVED] connector.rateLimitConfig was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — the entire shape is gone, not just this key: ConnectorRateLimitConfig and its RateLimitStrategy enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime security/rate-limit.ts) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute shared RateLimitConfig — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. 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.
retryConfig{ strategy?: Enum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }optionalRetry configuration
connectionTimeoutMsneveroptional[REMOVED] connector.connectionTimeoutMs was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the platform never honoured it and cannot honour it where it was declared: a connector's outbound call is a WHATWG fetch, whose only cancellation surface is one AbortSignal over the whole operation, so nothing there observes the connection phase separately, and the value only ever travelled (onto the reported def and the materialization fingerprint) without ever bounding a connect. Delete the key. Use requestTimeoutMs for the deadline the platform does keep — it is applied as resilientFetch's per-attempt timeout — and bound the connect phase at a connector provider or upstream gateway on a transport that can separate the phases. 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.
requestTimeoutMsnumberoptional (default: 30000)Request timeout in ms
statusneveroptional[REMOVED] connector.status was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: status: 'active' neither enabled nor advertised a connector, and 'error' or 'configuring' changed nothing either. Delete the key; the ConnectorStatus enum leaves with it. On a declarative entry, enabled: false is what withdraws a materialized instance or marks a catalog-only descriptor, and whether a registered connector can be dispatched is computed by the runtime and reported as state (ready or degraded) on GET /api/v1/automation/connectors — no authored value sets it. 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.
enabledbooleanoptional (default: true)Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor.
errorMappingneveroptional[REMOVED] connector.errorMapping was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no provider, dispatcher or materializer mapped an external error through the rules, so unmappedBehavior configured nothing and a rule's userMessage was never shown to anyone (that spelling is the live API-error channel, ApiError.userMessage, which a thrown HTTP error declares — not connector metadata). Delete the key; the whole shape leaves with it (ErrorMappingConfig, ErrorMappingRule and the ConnectorErrorCategory enum). There is no replacement, because no error-mapping engine exists: a connector's failures reach callers as the provider's own errors (ADR-0097). 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.
healthneveroptional[REMOVED] connector.health was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no connector health probe or circuit breaker ever existed: nothing scheduled a healthCheck request, counted consecutive failures against a threshold, or opened, half-opened or closed a circuitBreaker, and no fallbackStrategy was ever applied, so every key in the block configured nothing. That includes circuitBreaker.monitoringWindowMs and the monitoringWindow spelling it was renamed from: the renamed key is removed with the rest. Delete the key; the whole shape leaves with it (ConnectorHealth, HealthCheckConfig, CircuitBreakerConfig). Whether a connector can be dispatched is computed, not authored: GET /api/v1/automation/connectors reports each connector's state (ready or degraded). Put health probes and circuit breaking in the connector provider or an upstream gateway. 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.
metadataRecord<string, any>optionalCustom connector metadata
_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: DeclarativeConnectorEntry.authentication[type='oauth2']

PropertyTypeRequiredDescription
type'oauth2'✅
authorizationUrlstring✅OAuth2 authorization endpoint
tokenUrlstring✅OAuth2 token endpoint
clientIdstring✅OAuth2 client ID
clientSecretstring✅OAuth2 client secret (typically from ENV)
scopesstring[]optionalRequested OAuth2 scopes
redirectUristringoptionalOAuth2 redirect URI
refreshTokenstringoptionalRefresh token for token renewal
tokenExpiryintegeroptionalToken expiry timestamp (Unix milliseconds)

Nested Shape: DeclarativeConnectorEntry.authentication[type='api-key']

PropertyTypeRequiredDescription
type'api-key'✅
keystring✅API key value
headerNamestringoptional (default: "X-API-Key")HTTP header name for API key
paramNamestringoptionalQuery parameter name (alternative to header)

Nested Shape: DeclarativeConnectorEntry.authentication[type='basic']

PropertyTypeRequiredDescription
type'basic'✅
usernamestring✅Username
passwordstring✅Password

Nested Shape: DeclarativeConnectorEntry.authentication[type='bearer']

PropertyTypeRequiredDescription
type'bearer'✅
tokenstring✅Bearer token

Nested Shape: DeclarativeConnectorEntry.auth[type='bearer']

PropertyTypeRequiredDescription
type'bearer'✅
credentialRefstring✅Secrets-layer reference (e.g. an env-var name in the open tier) resolved to the bearer token at materialization. Never an inline token.

Nested Shape: DeclarativeConnectorEntry.auth[type='api-key']

PropertyTypeRequiredDescription
type'api-key'✅
credentialRefstring✅Secrets-layer reference resolved to the API key at materialization. Never an inline key.
headerNamestringoptionalHTTP header carrying the key (default X-API-Key).
paramNamestringoptionalQuery parameter carrying the key (alternative to header).

Nested Shape: DeclarativeConnectorEntry.auth[type='basic']

PropertyTypeRequiredDescription
type'basic'✅
usernamestring✅Username (not a secret; safe to keep in metadata).
credentialRefstring✅Secrets-layer reference resolved to the password at materialization. Never an inline password.

Nested Shape: DeclarativeConnectorEntry.actions[number]

PropertyTypeRequiredDescription
keystring✅Action key (machine name)
labelstring✅Human readable label
descriptionstringoptional
inputSchemaRecord<string, any>optionalInput parameters schema (JSON Schema)
outputSchemaRecord<string, any>optionalOutput schema (JSON Schema)
effectEnum<'read' | 'write'>optionalWhat the action does upstream: 'read' never mutates (reports acted:0); 'write' does (a successful dispatch reports acted:1). Omit when the effect is not knowable — the step is then reported as unmeasured, not as zero

Nested Shape: DeclarativeConnectorEntry.retryConfig

PropertyTypeRequiredDescription
strategyEnum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'>optional (default: "exponential_backoff")Retry strategy
maxAttemptsnumberoptional (default: 3)Maximum retry attempts
initialDelayMsnumberoptional (default: 1000)Initial retry delay in ms
maxDelayMsnumberoptional (default: 60000)Maximum retry delay in ms
backoffMultipliernumberoptional (default: 2)Exponential backoff multiplier
retryableStatusCodesnumber[]optional (default: [408,429,500,502,503,504])HTTP status codes to retry
retryOnNetworkErrorbooleanoptional (default: true)Retry on network errors
jitterbooleanoptional (default: true)Add jitter to retry delays

RetryConfig

Properties

PropertyTypeRequiredDescription
strategyEnum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'>optional (default: "exponential_backoff")Retry strategy
maxAttemptsnumberoptional (default: 3)Maximum retry attempts
initialDelayMsnumberoptional (default: 1000)Initial retry delay in ms
maxDelayMsnumberoptional (default: 60000)Maximum retry delay in ms
backoffMultipliernumberoptional (default: 2)Exponential backoff multiplier
retryableStatusCodesnumber[]optional (default: [408,429,500,502,503,504])HTTP status codes to retry
retryOnNetworkErrorbooleanoptional (default: true)Retry on network errors
jitterbooleanoptional (default: true)Add jitter to retry delays

On this page

What this layer does NOT provideRuntime contract — descriptor vs. registered connector (#2612)When to Use This LayerThere is no "Trigger Registry" alternativeTypeScript UsageConnectorPropertiesNested Shape: Connector.authentication[type='oauth2']Nested Shape: Connector.authentication[type='api-key']Nested Shape: Connector.authentication[type='basic']Nested Shape: Connector.authentication[type='bearer']Nested Shape: Connector.auth[type='bearer']Nested Shape: Connector.auth[type='api-key']Nested Shape: Connector.auth[type='basic']Nested Shape: Connector.actions[number]Nested Shape: Connector.retryConfigConnectorActionPropertiesConnectorActionEffectAllowed ValuesConnectorInstanceAPIKeyAuthPropertiesConnectorInstanceAuthUnion OptionsOption 1PropertiesOption 2PropertiesOption 3PropertiesOption 4PropertiesConnectorInstanceBasicAuthPropertiesConnectorInstanceBearerAuthPropertiesConnectorInstanceNoAuthPropertiesConnectorRetryStrategyAllowed ValuesConnectorTypeAllowed ValuesDeclarativeConnectorEntryPropertiesNested Shape: DeclarativeConnectorEntry.authentication[type='oauth2']Nested Shape: DeclarativeConnectorEntry.authentication[type='api-key']Nested Shape: DeclarativeConnectorEntry.authentication[type='basic']Nested Shape: DeclarativeConnectorEntry.authentication[type='bearer']Nested Shape: DeclarativeConnectorEntry.auth[type='bearer']Nested Shape: DeclarativeConnectorEntry.auth[type='api-key']Nested Shape: DeclarativeConnectorEntry.auth[type='basic']Nested Shape: DeclarativeConnectorEntry.actions[number]Nested Shape: DeclarativeConnectorEntry.retryConfigRetryConfigProperties