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:
- Runtime registration (plugin-only). The automation engine's connector
registry — what
GET /connectorslists and theconnector_actionflow node dispatches — is populated exclusively by plugins callingengine.registerConnector(def, handlers)with a handler per declared action (ADR-0018 §Addendum). The definition is validated against this schema at registration. - 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 withactionsthat lack a same-name runtime registration; mark deliberate catalog-only entries withenabled: 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 atautomation/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
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique connector identifier |
| label | string | ✅ | Display label |
| type | Enum<'saas' | 'database' | 'file_storage' | 'message_queue' | 'api' | 'custom'> | ✅ | Connector type |
| description | string | optional | Connector description |
| icon | string | optional | Icon 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. |
| provider | string | optional | Generic-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). |
| providerConfig | Record<string, any> | optional | Provider-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 } | optional | Declarative 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 | |
| triggers | never | optional | [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. |
| syncConfig | never | optional | [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. |
| fieldMappings | never | optional | [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. |
| webhooks | never | optional | [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. |
| rateLimitConfig | never | optional | [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; … } | optional | Retry configuration |
| connectionTimeoutMs | never | optional | [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. |
| requestTimeoutMs | number | optional (default: 30000) | Request timeout in ms |
| status | never | optional | [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. |
| enabled | boolean | optional (default: true) | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor. |
| errorMapping | never | optional | [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. |
| health | never | optional | [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. |
| metadata | Record<string, any> | optional | Custom connector metadata |
| _lock | Enum<'none' | 'no-overlay' | 'no-delete' | 'full'> | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| _lockReason | string | optional | Human-readable reason shown when a write is refused by _lock. |
| _lockSource | Enum<'artifact' | 'package' | 'env-forced'> | optional | Layer that set _lock (artifact | package | env-forced). |
| _provenance | Enum<'package' | 'org' | 'env-forced'> | optional | Origin of the item (package | org | env-forced). |
| _packageId | string | optional | Owning package machine id. |
| _packageVersion | string | optional | Owning package version. |
| _lockDocsUrl | string | optional | Optional documentation link surfaced next to _lockReason. |
Nested Shape: Connector.authentication[type='oauth2']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'oauth2' | ✅ | |
| authorizationUrl | string | ✅ | OAuth2 authorization endpoint |
| tokenUrl | string | ✅ | OAuth2 token endpoint |
| clientId | string | ✅ | OAuth2 client ID |
| clientSecret | string | ✅ | OAuth2 client secret (typically from ENV) |
| scopes | string[] | optional | Requested OAuth2 scopes |
| redirectUri | string | optional | OAuth2 redirect URI |
| refreshToken | string | optional | Refresh token for token renewal |
| tokenExpiry | integer | optional | Token expiry timestamp (Unix milliseconds) |
Nested Shape: Connector.authentication[type='api-key']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'api-key' | ✅ | |
| key | string | ✅ | API key value |
| headerName | string | optional (default: "X-API-Key") | HTTP header name for API key |
| paramName | string | optional | Query parameter name (alternative to header) |
Nested Shape: Connector.authentication[type='basic']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'basic' | ✅ | |
| username | string | ✅ | Username |
| password | string | ✅ | Password |
Nested Shape: Connector.authentication[type='bearer']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'bearer' | ✅ | |
| token | string | ✅ | Bearer token |
Nested Shape: Connector.auth[type='bearer']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'bearer' | ✅ | |
| credentialRef | string | ✅ | 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']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'api-key' | ✅ | |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the API key at materialization. Never an inline key. |
| headerName | string | optional | HTTP header carrying the key (default X-API-Key). |
| paramName | string | optional | Query parameter carrying the key (alternative to header). |
Nested Shape: Connector.auth[type='basic']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'basic' | ✅ | |
| username | string | ✅ | Username (not a secret; safe to keep in metadata). |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the password at materialization. Never an inline password. |
Nested Shape: Connector.actions[number]
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | ✅ | Action key (machine name) |
| label | string | ✅ | Human readable label |
| description | string | optional | |
| inputSchema | Record<string, any> | optional | Input parameters schema (JSON Schema) |
| outputSchema | Record<string, any> | optional | Output schema (JSON Schema) |
| effect | Enum<'read' | 'write'> | optional | 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 |
Nested Shape: Connector.retryConfig
| Property | Type | Required | Description |
|---|---|---|---|
| strategy | Enum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'> | optional (default: "exponential_backoff") | Retry strategy |
| maxAttempts | number | optional (default: 3) | Maximum retry attempts |
| initialDelayMs | number | optional (default: 1000) | Initial retry delay in ms |
| maxDelayMs | number | optional (default: 60000) | Maximum retry delay in ms |
| backoffMultiplier | number | optional (default: 2) | Exponential backoff multiplier |
| retryableStatusCodes | number[] | optional (default: [408,429,500,502,503,504]) | HTTP status codes to retry |
| retryOnNetworkError | boolean | optional (default: true) | Retry on network errors |
| jitter | boolean | optional (default: true) | Add jitter to retry delays |
ConnectorAction
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | ✅ | Action key (machine name) |
| label | string | ✅ | Human readable label |
| description | string | optional | |
| inputSchema | Record<string, any> | optional | Input parameters schema (JSON Schema) |
| outputSchema | Record<string, any> | optional | Output schema (JSON Schema) |
| effect | Enum<'read' | 'write'> | optional | 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 |
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
readwrite
ConnectorInstanceAPIKeyAuth
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'api-key' | ✅ | |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the API key at materialization. Never an inline key. |
| headerName | string | optional | HTTP header carrying the key (default X-API-Key). |
| paramName | string | optional | Query parameter carrying the key (alternative to header). |
ConnectorInstanceAuth
Union Options
This schema accepts one of the following structures:
Option 1
Type: none
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'none' | ✅ |
Option 2
Type: bearer
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'bearer' | ✅ | |
| credentialRef | string | ✅ | 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
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'api-key' | ✅ | |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the API key at materialization. Never an inline key. |
| headerName | string | optional | HTTP header carrying the key (default X-API-Key). |
| paramName | string | optional | Query parameter carrying the key (alternative to header). |
Option 4
Type: basic
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'basic' | ✅ | |
| username | string | ✅ | Username (not a secret; safe to keep in metadata). |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the password at materialization. Never an inline password. |
ConnectorInstanceBasicAuth
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'basic' | ✅ | |
| username | string | ✅ | Username (not a secret; safe to keep in metadata). |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the password at materialization. Never an inline password. |
ConnectorInstanceBearerAuth
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'bearer' | ✅ | |
| credentialRef | string | ✅ | 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
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'none' | ✅ |
ConnectorRetryStrategy
Retry strategy
Allowed Values
exponential_backofflinear_backofffixed_delayno_retry
ConnectorType
Connector type
Allowed Values
saasdatabasefile_storagemessage_queueapicustom
DeclarativeConnectorEntry
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique connector identifier |
| label | string | ✅ | Display label |
| type | Enum<'saas' | 'database' | 'file_storage' | 'message_queue' | 'api' | 'custom'> | ✅ | Connector type |
| description | string | optional | Connector description |
| icon | string | optional | Icon 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. |
| provider | string | optional | Generic-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). |
| providerConfig | Record<string, any> | optional | Provider-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 } | optional | Declarative 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 | |
| triggers | never | optional | [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. |
| syncConfig | never | optional | [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. |
| fieldMappings | never | optional | [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. |
| webhooks | never | optional | [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. |
| rateLimitConfig | never | optional | [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; … } | optional | Retry configuration |
| connectionTimeoutMs | never | optional | [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. |
| requestTimeoutMs | number | optional (default: 30000) | Request timeout in ms |
| status | never | optional | [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. |
| enabled | boolean | optional (default: true) | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor. |
| errorMapping | never | optional | [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. |
| health | never | optional | [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. |
| metadata | Record<string, any> | optional | Custom connector metadata |
| _lock | Enum<'none' | 'no-overlay' | 'no-delete' | 'full'> | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| _lockReason | string | optional | Human-readable reason shown when a write is refused by _lock. |
| _lockSource | Enum<'artifact' | 'package' | 'env-forced'> | optional | Layer that set _lock (artifact | package | env-forced). |
| _provenance | Enum<'package' | 'org' | 'env-forced'> | optional | Origin of the item (package | org | env-forced). |
| _packageId | string | optional | Owning package machine id. |
| _packageVersion | string | optional | Owning package version. |
| _lockDocsUrl | string | optional | Optional documentation link surfaced next to _lockReason. |
Nested Shape: DeclarativeConnectorEntry.authentication[type='oauth2']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'oauth2' | ✅ | |
| authorizationUrl | string | ✅ | OAuth2 authorization endpoint |
| tokenUrl | string | ✅ | OAuth2 token endpoint |
| clientId | string | ✅ | OAuth2 client ID |
| clientSecret | string | ✅ | OAuth2 client secret (typically from ENV) |
| scopes | string[] | optional | Requested OAuth2 scopes |
| redirectUri | string | optional | OAuth2 redirect URI |
| refreshToken | string | optional | Refresh token for token renewal |
| tokenExpiry | integer | optional | Token expiry timestamp (Unix milliseconds) |
Nested Shape: DeclarativeConnectorEntry.authentication[type='api-key']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'api-key' | ✅ | |
| key | string | ✅ | API key value |
| headerName | string | optional (default: "X-API-Key") | HTTP header name for API key |
| paramName | string | optional | Query parameter name (alternative to header) |
Nested Shape: DeclarativeConnectorEntry.authentication[type='basic']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'basic' | ✅ | |
| username | string | ✅ | Username |
| password | string | ✅ | Password |
Nested Shape: DeclarativeConnectorEntry.authentication[type='bearer']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'bearer' | ✅ | |
| token | string | ✅ | Bearer token |
Nested Shape: DeclarativeConnectorEntry.auth[type='bearer']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'bearer' | ✅ | |
| credentialRef | string | ✅ | 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']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'api-key' | ✅ | |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the API key at materialization. Never an inline key. |
| headerName | string | optional | HTTP header carrying the key (default X-API-Key). |
| paramName | string | optional | Query parameter carrying the key (alternative to header). |
Nested Shape: DeclarativeConnectorEntry.auth[type='basic']
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'basic' | ✅ | |
| username | string | ✅ | Username (not a secret; safe to keep in metadata). |
| credentialRef | string | ✅ | Secrets-layer reference resolved to the password at materialization. Never an inline password. |
Nested Shape: DeclarativeConnectorEntry.actions[number]
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | ✅ | Action key (machine name) |
| label | string | ✅ | Human readable label |
| description | string | optional | |
| inputSchema | Record<string, any> | optional | Input parameters schema (JSON Schema) |
| outputSchema | Record<string, any> | optional | Output schema (JSON Schema) |
| effect | Enum<'read' | 'write'> | optional | 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 |
Nested Shape: DeclarativeConnectorEntry.retryConfig
| Property | Type | Required | Description |
|---|---|---|---|
| strategy | Enum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'> | optional (default: "exponential_backoff") | Retry strategy |
| maxAttempts | number | optional (default: 3) | Maximum retry attempts |
| initialDelayMs | number | optional (default: 1000) | Initial retry delay in ms |
| maxDelayMs | number | optional (default: 60000) | Maximum retry delay in ms |
| backoffMultiplier | number | optional (default: 2) | Exponential backoff multiplier |
| retryableStatusCodes | number[] | optional (default: [408,429,500,502,503,504]) | HTTP status codes to retry |
| retryOnNetworkError | boolean | optional (default: true) | Retry on network errors |
| jitter | boolean | optional (default: true) | Add jitter to retry delays |
RetryConfig
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| strategy | Enum<'exponential_backoff' | 'linear_backoff' | 'fixed_delay' | 'no_retry'> | optional (default: "exponential_backoff") | Retry strategy |
| maxAttempts | number | optional (default: 3) | Maximum retry attempts |
| initialDelayMs | number | optional (default: 1000) | Initial retry delay in ms |
| maxDelayMs | number | optional (default: 60000) | Maximum retry delay in ms |
| backoffMultiplier | number | optional (default: 2) | Exponential backoff multiplier |
| retryableStatusCodes | number[] | optional (default: [408,429,500,502,503,504]) | HTTP status codes to retry |
| retryOnNetworkError | boolean | optional (default: true) | Retry on network errors |
| jitter | boolean | optional (default: true) | Add jitter to retry delays |
Integration Protocol
The ObjectStack Integration Protocol in 1 reference page: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example.
Kernel Protocol
The ObjectStack Kernel Protocol in 30 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example.