| title | API Reference |
|---|---|
| description | Complete REST API reference for ObjectStack — all endpoints, request/response schemas, and service availability. |
ObjectStack exposes a fully typed REST API. All endpoints use JSON request/response bodies. The API is service-driven — routes are only available when the corresponding plugin is installed. Use the Discovery endpoint to determine what services are available at runtime.
**Base URL**: Configurable, defaults to `/api/v1`. All paths below are relative to the base URL.The discovery endpoint is the entry point for all clients. It returns the API version, available routes, service capabilities, and per-service status.
Returns the full discovery manifest.
Response:
{
"name": "ObjectOS",
"version": "1.0.0",
"environment": "development",
"routes": {
"data": "/api/v1/data",
"metadata": "/api/v1/meta",
"analytics": "/api/v1/analytics",
"auth": null,
"workflow": null
},
"features": {
"graphql": false,
"search": false,
"websockets": false,
"files": false,
"analytics": true,
"ai": false,
"workflow": false,
"notifications": false,
"i18n": false
},
"services": {
"metadata": {
"enabled": true,
"status": "degraded",
"route": "/api/v1/meta",
"provider": "kernel",
"message": "In-memory registry; DB persistence pending"
},
"data": { "enabled": true, "status": "available", "route": "/api/v1/data", "provider": "kernel" },
"analytics": { "enabled": true, "status": "available", "route": "/api/v1/analytics" },
"auth": { "enabled": false, "status": "unavailable", "message": "Install an auth plugin to enable" }
},
"locale": {
"default": "en",
"supported": ["en", "zh-CN"],
"timezone": "UTC"
}
}Alias for the discovery endpoint used by auto-discovery in the client SDK.
**Service Status Values**: `available` (fully operational), `registered` (route declared but handler unverified — may return 501), `degraded` (partial functionality), `unavailable` (not installed), `stub` (placeholder that throws errors)Retrieve and manage object schemas, metadata types, and UI views. Always available — provided by the kernel.
List all registered metadata types.
Response: { types: ["object", "view", "plugin", ...] }
List all items of a metadata type.
| Parameter | Location | Description |
|---|---|---|
type |
path | Metadata type name (e.g. object, view) |
Response: { type: "object", items: [{ name: "account", ... }, ...] }
Get a specific metadata item with optional ETag caching.
| Parameter | Location | Description |
|---|---|---|
type |
path | Metadata type name |
name |
path | Item name (snake_case) |
If-None-Match |
header | ETag for conditional request |
Response: { type: "object", name: "account", item: { ... } }
304 if ETag matches (not modified).
Create or update a metadata item.
Body: { type: "object", name: "account", item: { ... } }
Response: { success: true }
Get an auto-generated UI view for an object.
| Parameter | Location | Description |
|---|---|---|
object |
path | Object name (snake_case) |
type |
path | list or form |
Response: Full ViewSchema definition with columns/fields derived from the object schema.
CRUD operations on any object. Always available — provided by the kernel.
Query records with filtering, sorting, selection, and pagination.
| Parameter | Location | Description |
|---|---|---|
object |
path | Object name |
select |
query | Comma-separated field names |
filter |
query | Filter expression (JSON). filters also accepted for backward compatibility. |
sort |
query | Sort expression (e.g. name asc or -created_at) |
top |
query | Limit (default: 20) |
skip |
query | Offset |
expand |
query | Comma-separated list of relations to eager-load |
search |
query | Full-text search query |
Note: OData-style
$-prefixed parameters ($filter,$select,$orderby,$top,$skip) are supported via the OData endpoint. The standard REST API uses unprefixed parameter names.
Response:
{
"object": "account",
"records": [{ "id": "1", "name": "Acme Corp", ... }],
"total": 42,
"hasMore": true
}Get a single record by ID. Only select and expand query parameters are allowed; all other parameters are discarded.
| Parameter | Location | Description |
|---|---|---|
object |
path | Object name |
id |
path | Record ID |
select |
query | Comma-separated field names to include |
expand |
query | Comma-separated list of relations to eager-load |
Response: { object: "account", id: "1", record: { ... } }
Create a new record.
Body: { name: "Acme Corp", industry: "Technology" }
Response: { object: "account", id: "1", record: { ... } }
Update an existing record (partial update).
Body: { industry: "Healthcare" }
Response: { object: "account", id: "1", record: { ... } }
Delete a record.
Response: { object: "account", id: "1", success: true }
Efficient bulk operations. Always available.
Execute a batch operation (create / update / upsert / delete) on multiple records.
Body:
{
"operation": "update",
"records": [
{ "id": "1", "data": { "status": "active" } },
{ "id": "2", "data": { "status": "active" } }
],
"options": {
"atomic": true,
"returnRecords": true,
"continueOnError": false,
"validateOnly": false
}
}Response: BatchUpdateResponse with succeeded, failed, total, and a per-record results array. Each entry in results has id, success, an optional errors array, and optional data (the full record, present when returnRecords is true).
Batch create multiple records.
Body: a bare array of records — [{ name: "A" }, { name: "B" }]. The REST handler reads the request body directly as the records array, so do not wrap it in { records: [...] }.
Response: { object: "account", records: [...], count: 2 }
Batch update multiple records.
Body:
{
"records": [
{ "id": "1", "data": { "status": "active" } },
{ "id": "2", "data": { "status": "closed" } }
],
"options": { "atomic": false }
}Batch delete records by ID list.
Body: { ids: ["1", "2", "3"] }
Clone a record. Reads the source, drops engine-owned columns (id, the audit
fields, autonumbers, and computed formula/summary values) so they are
re-derived, applies any caller overrides, and inserts the copy. Shallow by
design — it duplicates the record's own fields, not its child records.
Gated by the object's enable.clone capability (default true); an object with
enable.clone: false returns 403 CLONE_DISABLED.
Body (optional): { "overrides": { "name": "Acme (Copy)" } } — applied on
top of the copied values (a bare field map is also accepted). The natural place
to set a new name or clear a unique field.
Response 201: { object, id, sourceId, record }
Semantic BI queries using a cube-style API. Available when the analytics service is enabled.
Execute an analytics query.
Body:
{
"cube": "account",
"measures": ["revenue.sum", "count"],
"dimensions": ["industry"],
"where": { "status": "active" },
"limit": 100
}Response: the runtime dispatcher wraps the AnalyticsResult as { success: true, data: { rows, fields, sql?, totals? } }:
{
"success": true,
"data": {
"rows": [
{ "industry": "Technology", "revenue.sum": 150000, "count": 5 },
{ "industry": "Healthcare", "revenue.sum": 80000, "count": 3 }
],
"fields": [
{ "name": "industry", "type": "string", "label": "Industry" },
{ "name": "revenue.sum", "type": "number", "label": "Revenue Sum", "format": "$0,0" },
{ "name": "count", "type": "number", "label": "Count" }
],
"sql": "SELECT ..."
}
}Get auto-generated cube metadata for all objects.
Response: Array of cube definitions with measures, dimensions, and time dimensions.
Execute a raw SQL analytics query (if supported by driver).
Manage installed plugins/packages.
List installed packages.
Install a package from manifest.
Body: { manifest: { name: "plugin-auth", version: "1.0.0", ... } }
Get package details.
Uninstall a package.
Enable a disabled package.
Disable a package without uninstalling.
These endpoints are only available when an auth plugin is installed. Check `discovery.services.auth.enabled` first.
Authenticate and receive a session token.
Body: { username: "admin", password: "secret" }
Response: { token: "jwt...", expiresAt: "...", user: { ... } }
The following endpoints become available when the corresponding plugin is installed and registered with the kernel. Use the discovery services map to check availability.
| Method | Endpoint | Description |
|---|---|---|
| GET | /workflow/:object/config |
Get workflow configuration |
| GET | /workflow/:object/:recordId/state |
Get record's workflow state |
| POST | /workflow/:object/:recordId/transition |
Execute state transition |
| POST | /workflow/:object/:recordId/approve |
Approve workflow step |
| POST | /workflow/:object/:recordId/reject |
Reject workflow step |
| Method | Endpoint | Description |
|---|---|---|
| POST | /automation/trigger/:name |
Trigger an automation flow by name |
| Method | Endpoint | Description |
|---|---|---|
| GET | /ui/views?object=:object |
List views for an object |
| GET | /ui/views/:viewId |
Get a view definition |
| POST | /ui/views |
Create a new view |
| PATCH | /ui/views/:viewId |
Update a view |
| DELETE | /ui/views/:viewId |
Delete a view |
| Method | Endpoint | Description |
|---|---|---|
| POST | /realtime/connect |
Establish WebSocket/SSE connection |
| POST | /realtime/disconnect |
Close connection |
| POST | /realtime/subscribe |
Subscribe to channel |
| POST | /realtime/unsubscribe |
Unsubscribe |
| POST | /realtime/presence |
Set presence |
| GET | /realtime/presence/:channel |
Get channel presence |
| Method | Endpoint | Description |
|---|---|---|
| GET | /notifications |
List notifications (inbox) |
| POST | /notifications/read |
Mark notifications as read (body: { ids: string[] }) |
| POST | /notifications/read/all |
Mark all as read |
| Method | Endpoint | Description |
|---|---|---|
| POST | /ai/nlq |
Natural language → query |
| POST | /ai/chat |
AI chat conversation |
| POST | /ai/suggest |
Get value suggestions |
| POST | /ai/insights |
Get data insights |
| Method | Endpoint | Description |
|---|---|---|
| GET | /i18n/locales |
List available locales |
| GET | /i18n/translations/:locale |
Get translation bundle |
| GET | /i18n/labels/:object/:locale |
Get field labels |
| Method | Endpoint | Description |
|---|---|---|
| POST | /graphql |
Execute GraphQL query/mutation |
| Method | Endpoint | Description |
|---|---|---|
| POST | /storage/upload |
Upload a file |
| GET | /storage/file/:id |
Download a file |
Error responses depend on which HTTP server is in front of the kernel. There are two wire formats in use today.
Kernel REST server (@objectstack/rest) emits a string error message plus a SCREAMING_SNAKE code:
{
"error": "Record not found: account/123",
"code": "RECORD_NOT_FOUND"
}Validation failures additionally include an issues array. Common codes emitted by the kernel REST server:
| Code | HTTP | Description |
|---|---|---|
VALIDATION_FAILED |
400 | Input validation failed (includes issues) |
PERMISSION_DENIED |
403 | Insufficient permissions |
RECORD_NOT_FOUND |
404 | Resource does not exist |
CONCURRENT_UPDATE |
409 | Record was modified by another user |
Runtime dispatcher (@objectstack/runtime) wraps errors in the { success: false, ... } envelope, where code is the numeric HTTP status:
{
"success": false,
"error": {
"message": "Record not found: account/123",
"code": 404,
"details": {}
}
}All request/response schemas are defined as Zod schemas in @objectstack/spec/api and can be used for both runtime validation and TypeScript type inference.
import {
FindDataRequestSchema,
FindDataResponseSchema,
type FindDataRequest,
type FindDataResponse,
} from '@objectstack/spec/api';
// Runtime validation
const request = FindDataRequestSchema.parse({ object: 'account', query: { ... } });
// TypeScript type
const response: FindDataResponse = await protocol.findData(request);See the Protocol Reference for the full list of protocol methods and their Zod schemas.