ObjectStackObjectStack

Plugin Rest Api — API Protocol reference

Defines the schema for REST API plugins that register Discovery, Metadata, Data CRUD, Batch, and Permission routes with the HTTP Dispatcher.

REST API Plugin Protocol

Defines the schema for REST API plugins that register Discovery, Metadata, Data CRUD, Batch, and Permission routes with the HTTP Dispatcher.

This plugin type implements Phase 2 of the API Protocol implementation plan, providing standardized REST endpoints with:

  • Request validation middleware using Zod schemas
  • Response envelope wrapping with BaseResponseSchema
  • Error handling using ApiErrorSchema
  • OpenAPI documentation auto-generation

Features:

  • Route registration for core API endpoints
  • Automatic schema-based validation
  • Standardized request/response envelopes
  • OpenAPI/Swagger documentation generation

Architecture Alignment:

  • Salesforce: REST API with metadata and data CRUD
  • Microsoft Dynamics: Web API with entity operations
  • Strapi: Auto-generated REST endpoints from schemas

Serving routes from a plugin (imperative http.server mount)

// Routes are mounted in CODE — resolve the `http.server` service from the
// plugin context and register handlers on `kernel:ready` (the service is
// registered by plugin-hono-server; `examples/app-showcase`'s
// recalc-endpoint is a real consumer of this exact shape). The worked
// manifest example that used to sit here declared `contributes.routes`,
// which was removed in @objectstack/spec 17 (commit bc56e1881): nothing ever read
// it, so every route it showed parsed cleanly and served nothing.
class RestApiPlugin {
  name = 'rest_api';
  async init(ctx: PluginContext) {
    ctx.hook('kernel:ready', async () => {
      const server = await ctx.getService<IHttpServer>('http.server');
      server.get('/api/v1/discovery', (req, res) => { ... });
      server.post('/api/v1/data/:object', (req, res) => { ... });
    });
  }
}

A declarative endpoint over a pipeline the platform already runs (query/return records, trigger a flow) is defineStack({ apis }) instead — no plugin code at all.

Source: packages/spec/src/api/plugin-rest-api.zod.ts

TypeScript Usage

import { ErrorHandlingConfigSchema, OpenApiGenerationConfigSchema, RequestValidationConfigSchema, ResponseEnvelopeConfigSchema, RestApiEndpointSchema, RestApiPluginConfigSchema, RestApiRouteCategory, RestApiRouteRegistrationSchema, ValidationMode } from '@objectstack/spec/api';
import type { ErrorHandlingConfig, OpenApiGenerationConfig, RequestValidationConfig, ResponseEnvelopeConfig, RestApiEndpoint, RestApiPluginConfig, RestApiRouteCategory, RestApiRouteRegistration, ValidationMode } from '@objectstack/spec/api';

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

ErrorHandlingConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable standardized error handling
includeStackTracebooleanoptional (default: false)Include stack traces in error responses
logErrorsbooleanoptional (default: true)Log errors to system logger
exposeInternalErrorsbooleanoptional (default: false)Expose internal error details in responses
includeRequestIdbooleanoptional (default: true)Include requestId in error responses
includeTimestampbooleanoptional (default: true)Include timestamp in error responses
includeDocumentationbooleanoptional (default: true)Include documentation URLs for errors
documentationBaseUrlstringoptionalBase URL for error documentation
customErrorMessagesRecord<string, string>optionalCustom error messages by error code
redactFieldsstring[]optionalField names to redact from error details

OpenApiGenerationConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable automatic OpenAPI documentation generation
versionEnum<'3.0.0' | '3.0.1' | '3.0.2' | '3.0.3' | '3.1.0'>optional (default: "3.0.3")OpenAPI specification version
titlestringoptional (default: "ObjectStack API")API title
descriptionstringoptionalAPI description
apiVersionstringoptional (default: "1.0.0")API version
outputPathstringoptional (default: "/api/docs/openapi.json")URL path to serve OpenAPI JSON
uiPathstringoptional (default: "/api/docs")URL path to serve documentation UI
uiFrameworkEnum<'swagger-ui' | 'redoc' | 'rapidoc' | 'elements'>optional (default: "swagger-ui")Documentation UI framework
includeInternalbooleanoptional (default: false)Include internal endpoints in documentation
generateSchemasbooleanoptional (default: true)Auto-generate schemas from Zod definitions
includeExamplesbooleanoptional (default: true)Include request/response examples
servers{ url: string; description?: string }[]optionalServer URLs for API
contact{ name?: string; url?: string; email?: string }optionalAPI contact information
license{ name: string; url?: string }optionalAPI license information
securitySchemesRecord<string, { type: Enum<'apiKey' | 'http' | 'oauth2' | 'openIdConnect'>; scheme?: string; bearerFormat?: string }>optionalSecurity scheme definitions

Nested Shape: OpenApiGenerationConfig.servers[number]

PropertyTypeRequiredDescription
urlstring✅Server URL
descriptionstringoptionalServer description

Nested Shape: OpenApiGenerationConfig.license

PropertyTypeRequiredDescription
namestring✅License name
urlstringoptionalLicense URL

RequestValidationConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable automatic request validation
modeEnum<'strict' | 'permissive' | 'strip'>optional (default: "strict")How to handle validation errors
validateBodybooleanoptional (default: true)Validate request body against schema
validateQuerybooleanoptional (default: true)Validate query string parameters
validateParamsbooleanoptional (default: true)Validate URL path parameters
validateHeadersbooleanoptional (default: false)Validate request headers
includeFieldErrorsbooleanoptional (default: true)Include field-level error details in response
errorPrefixstringoptionalCustom prefix for validation error messages
schemaRegistrystringoptionalSchema registry name to use for validation

ResponseEnvelopeConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable automatic response envelope wrapping
includeMetadatabooleanoptional (default: true)Include meta object in responses
includeTimestampbooleanoptional (default: true)Include timestamp in response metadata
includeRequestIdbooleanoptional (default: true)Include requestId in response metadata
includeDurationbooleanoptional (default: false)Include request duration in ms
includeTraceIdbooleanoptional (default: false)Include distributed traceId
customMetadataRecord<string, any>optionalAdditional metadata fields to include
skipIfWrappedbooleanoptional (default: true)Skip wrapping if response already has success field

RestApiEndpoint

Properties

PropertyTypeRequiredDescription
methodEnum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>✅HTTP method for this endpoint
pathstring✅URL path pattern (e.g., /api/v1/data/:object/:id)
handlerstring✅Protocol method name or handler identifier
categoryEnum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | 'automation' | 'ui' | 'realtime' | 'notification' | 'ai' | 'i18n'>✅Route category
publicbooleanoptional (default: false)Is publicly accessible without authentication
permissionsstring[]optionalRequired permissions (e.g., ["data.read", "object.account.read"])
summarystringoptionalShort description for OpenAPI
descriptionstringoptionalDetailed description for OpenAPI
tagsstring[]optionalOpenAPI tags for grouping
requestSchemastringoptionalRequest schema name (for validation)
responseSchemastringoptionalResponse schema name (for documentation)
timeoutMsintegeroptionalRequest timeout in milliseconds
rateLimitstringoptionalRate limit policy name
cacheablebooleanoptional (default: false)Whether response can be cached
cacheTtlSecondsintegeroptionalCache TTL in seconds
timeoutneveroptional[REMOVED] RestApiEndpoint.timeout was renamed to timeoutMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and the neighbouring cache TTL two lines below is in SECONDS. Rename the key to timeoutMs; the value (milliseconds) is unchanged.
cacheTtlneveroptional[REMOVED] RestApiEndpoint.cacheTtl was renamed to cacheTtlSeconds in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and the neighbouring request timeout two lines above is in MILLISECONDS. Rename the key to cacheTtlSeconds; the value (seconds) is unchanged.
handlerStatusneveroptional[REMOVED] RestApiEndpoint.handlerStatus was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no registrar, dispatcher or adapter consulted the key, so an endpoint declared stub or planned was served exactly like an implemented one, and the 501 NOT_IMPLEMENTED its docstring promised is raised by the declarative-endpoint executor for a target it cannot serve, never from this field. Delete the key. An endpoint that has no handler yet is simply not registered; a declared-but-unbuilt route answering 501 is not a platform capability (ruling record, 2026-09-01).

RestApiPluginConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable REST API plugin
basePathstringoptional (default: "/api")Base path for all API routes
versionstringoptional (default: "v1")API version identifier
routes{ prefix: string; service: string; category: Enum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | …>; methods?: string[]; … }[]✅Route registrations
validation{ enabled?: boolean; mode?: Enum<'strict' | 'permissive' | 'strip'>; validateBody?: boolean; validateQuery?: boolean; … }optionalRequest validation configuration
responseEnvelope{ enabled?: boolean; includeMetadata?: boolean; includeTimestamp?: boolean; includeRequestId?: boolean; … }optionalResponse envelope configuration
errorHandling{ enabled?: boolean; includeStackTrace?: boolean; logErrors?: boolean; exposeInternalErrors?: boolean; … }optionalError handling configuration
openApi{ enabled?: boolean; version?: Enum<'3.0.0' | '3.0.1' | '3.0.2' | '3.0.3' | '3.1.0'>; title?: string; description?: string; … }optionalOpenAPI documentation configuration
globalMiddleware{ name: string; type: Enum<'authentication' | 'authorization' | 'logging' | 'validation' | 'transformation' | 'error' | 'custom'>; enabled?: boolean; order?: integer; … }[]optionalGlobal middleware stack
cors{ enabled?: boolean; origins?: string[]; methods?: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>[]; credentials?: boolean }optionalCORS configuration
performance{ enableCompression?: boolean; enableETag?: boolean; enableCaching?: boolean; defaultCacheTtlSeconds?: integer }optionalPerformance optimization settings

Nested Shape: RestApiPluginConfig.routes[number]

PropertyTypeRequiredDescription
prefixstring✅URL path prefix for this route group
servicestring✅Core service name (metadata, data, auth, etc.)
categoryEnum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | …>✅Primary category for this route group
methodsstring[]optionalProtocol method names implemented
endpoints{ method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; path: string; handler: string; category: Enum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | …>; … }[]optionalEndpoint definitions
middleware{ name: string; type: Enum<'authentication' | 'authorization' | 'logging' | 'validation' | 'transformation' | 'error' | 'custom'>; enabled?: boolean; order?: integer; … }[]optionalMiddleware stack for this route group
authRequiredbooleanoptional (default: true)Whether authentication is required by default
documentation{ title?: string; description?: string; tags?: string[] }optionalDocumentation metadata for this route group

Nested Shape: RestApiPluginConfig.validation

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable automatic request validation
modeEnum<'strict' | 'permissive' | 'strip'>optional (default: "strict")How to handle validation errors
validateBodybooleanoptional (default: true)Validate request body against schema
validateQuerybooleanoptional (default: true)Validate query string parameters
validateParamsbooleanoptional (default: true)Validate URL path parameters
validateHeadersbooleanoptional (default: false)Validate request headers
includeFieldErrorsbooleanoptional (default: true)Include field-level error details in response
errorPrefixstringoptionalCustom prefix for validation error messages
schemaRegistrystringoptionalSchema registry name to use for validation

Nested Shape: RestApiPluginConfig.responseEnvelope

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable automatic response envelope wrapping
includeMetadatabooleanoptional (default: true)Include meta object in responses
includeTimestampbooleanoptional (default: true)Include timestamp in response metadata
includeRequestIdbooleanoptional (default: true)Include requestId in response metadata
includeDurationbooleanoptional (default: false)Include request duration in ms
includeTraceIdbooleanoptional (default: false)Include distributed traceId
customMetadataRecord<string, any>optionalAdditional metadata fields to include
skipIfWrappedbooleanoptional (default: true)Skip wrapping if response already has success field

Nested Shape: RestApiPluginConfig.errorHandling

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable standardized error handling
includeStackTracebooleanoptional (default: false)Include stack traces in error responses
logErrorsbooleanoptional (default: true)Log errors to system logger
exposeInternalErrorsbooleanoptional (default: false)Expose internal error details in responses
includeRequestIdbooleanoptional (default: true)Include requestId in error responses
includeTimestampbooleanoptional (default: true)Include timestamp in error responses
includeDocumentationbooleanoptional (default: true)Include documentation URLs for errors
documentationBaseUrlstringoptionalBase URL for error documentation
customErrorMessagesRecord<string, string>optionalCustom error messages by error code
redactFieldsstring[]optionalField names to redact from error details

Nested Shape: RestApiPluginConfig.openApi

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)Enable automatic OpenAPI documentation generation
versionEnum<'3.0.0' | '3.0.1' | '3.0.2' | '3.0.3' | '3.1.0'>optional (default: "3.0.3")OpenAPI specification version
titlestringoptional (default: "ObjectStack API")API title
descriptionstringoptionalAPI description
apiVersionstringoptional (default: "1.0.0")API version
outputPathstringoptional (default: "/api/docs/openapi.json")URL path to serve OpenAPI JSON
uiPathstringoptional (default: "/api/docs")URL path to serve documentation UI
uiFrameworkEnum<'swagger-ui' | 'redoc' | 'rapidoc' | 'elements'>optional (default: "swagger-ui")Documentation UI framework
includeInternalbooleanoptional (default: false)Include internal endpoints in documentation
generateSchemasbooleanoptional (default: true)Auto-generate schemas from Zod definitions
includeExamplesbooleanoptional (default: true)Include request/response examples
servers{ url: string; description?: string }[]optionalServer URLs for API
contact{ name?: string; url?: string; email?: string }optionalAPI contact information
license{ name: string; url?: string }optionalAPI license information
securitySchemesRecord<string, { type: Enum<'apiKey' | 'http' | 'oauth2' | 'openIdConnect'>; scheme?: string; bearerFormat?: string }>optionalSecurity scheme definitions

Nested Shape: RestApiPluginConfig.globalMiddleware[number]

PropertyTypeRequiredDescription
namestring✅Middleware name (snake_case)
typeEnum<'authentication' | 'authorization' | 'logging' | 'validation' | 'transformation' | 'error' | 'custom'>✅Middleware type
enabledbooleanoptional (default: true)Whether middleware is enabled
orderintegeroptional (default: 100)Execution order priority
configRecord<string, any>optionalMiddleware configuration object
paths{ include?: string[]; exclude?: string[] }optionalPath filtering

Nested Shape: RestApiPluginConfig.performance

PropertyTypeRequiredDescription
enableCompressionbooleanoptional (default: true)Enable response compression
enableETagbooleanoptional (default: true)Enable ETag generation
enableCachingbooleanoptional (default: true)Enable HTTP caching
defaultCacheTtlSecondsintegeroptional (default: 300)Default cache TTL in seconds
defaultCacheTtlneveroptional[REMOVED] RestApiPluginConfig.performance.defaultCacheTtl was renamed to defaultCacheTtlSeconds in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to defaultCacheTtlSeconds; the value (seconds) is unchanged.

RestApiRouteCategory

Allowed Values

  • discovery
  • metadata
  • data
  • batch
  • permission
  • analytics
  • automation
  • ui
  • realtime
  • notification
  • ai
  • i18n

RestApiRouteRegistration

Properties

PropertyTypeRequiredDescription
prefixstring✅URL path prefix for this route group
servicestring✅Core service name (metadata, data, auth, etc.)
categoryEnum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | 'automation' | 'ui' | 'realtime' | 'notification' | 'ai' | 'i18n'>✅Primary category for this route group
methodsstring[]optionalProtocol method names implemented
endpoints{ method: Enum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>; path: string; handler: string; category: Enum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | …>; … }[]optionalEndpoint definitions
middleware{ name: string; type: Enum<'authentication' | 'authorization' | 'logging' | 'validation' | 'transformation' | 'error' | 'custom'>; enabled?: boolean; order?: integer; … }[]optionalMiddleware stack for this route group
authRequiredbooleanoptional (default: true)Whether authentication is required by default
documentation{ title?: string; description?: string; tags?: string[] }optionalDocumentation metadata for this route group

Nested Shape: RestApiRouteRegistration.endpoints[number]

PropertyTypeRequiredDescription
methodEnum<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'>✅HTTP method for this endpoint
pathstring✅URL path pattern (e.g., /api/v1/data/:object/:id)
handlerstring✅Protocol method name or handler identifier
categoryEnum<'discovery' | 'metadata' | 'data' | 'batch' | 'permission' | 'analytics' | …>✅Route category
publicbooleanoptional (default: false)Is publicly accessible without authentication
permissionsstring[]optionalRequired permissions (e.g., ["data.read", "object.account.read"])
summarystringoptionalShort description for OpenAPI
descriptionstringoptionalDetailed description for OpenAPI
tagsstring[]optionalOpenAPI tags for grouping
requestSchemastringoptionalRequest schema name (for validation)
responseSchemastringoptionalResponse schema name (for documentation)
timeoutMsintegeroptionalRequest timeout in milliseconds
rateLimitstringoptionalRate limit policy name
cacheablebooleanoptional (default: false)Whether response can be cached
cacheTtlSecondsintegeroptionalCache TTL in seconds
timeoutneveroptional[REMOVED] RestApiEndpoint.timeout was renamed to timeoutMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and the neighbouring cache TTL two lines below is in SECONDS. Rename the key to timeoutMs; the value (milliseconds) is unchanged.
cacheTtlneveroptional[REMOVED] RestApiEndpoint.cacheTtl was renamed to cacheTtlSeconds in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and the neighbouring request timeout two lines above is in MILLISECONDS. Rename the key to cacheTtlSeconds; the value (seconds) is unchanged.
handlerStatusneveroptional[REMOVED] RestApiEndpoint.handlerStatus was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no registrar, dispatcher or adapter consulted the key, so an endpoint declared stub or planned was served exactly like an implemented one, and the 501 NOT_IMPLEMENTED its docstring promised is raised by the declarative-endpoint executor for a target it cannot serve, never from this field. Delete the key. An endpoint that has no handler yet is simply not registered; a declared-but-unbuilt route answering 501 is not a platform capability (ruling record, 2026-09-01).

Nested Shape: RestApiRouteRegistration.middleware[number]

PropertyTypeRequiredDescription
namestring✅Middleware name (snake_case)
typeEnum<'authentication' | 'authorization' | 'logging' | 'validation' | 'transformation' | 'error' | 'custom'>✅Middleware type
enabledbooleanoptional (default: true)Whether middleware is enabled
orderintegeroptional (default: 100)Execution order priority
configRecord<string, any>optionalMiddleware configuration object
paths{ include?: string[]; exclude?: string[] }optionalPath filtering

Nested Shape: RestApiRouteRegistration.documentation

PropertyTypeRequiredDescription
titlestringoptionalRoute group title
descriptionstringoptionalRoute group description
tagsstring[]optionalOpenAPI tags

ValidationMode

Allowed Values

  • strict
  • permissive
  • strip

On this page