Appearance
REST/OpenAPI bridge
Status: target state, partial. The contract is fixed in
ACTOR-COMMUNICATION-CONTRACT.md. The HTTP gateway today exposes only its own ops (gateway.http.*) and a curated set of platform routes (/api/auth/...,/api/identity/...,/api/apps). A general "any actor accepts becomes a REST POST" bridge is not in production code; this page documents the target.
The mapping
Source:
WORKSTREAMS/core-and-packaging/ACTOR-COMMUNICATION-CONTRACT.md§ "How they map to protocol gateways".
| Matrix declaration | OpenAPI primitive |
|---|---|
static accepts | POST /api/<root>/<mount>/<op> endpoints |
static state | GET /api/<root>/<mount>/$state/<key> endpoints |
static emits | GET /api/<root>/<mount>/$events SSE endpoints |
static blackboard | GET and PUT /api/<root>/<mount>/$blackboard/<factName> |
static streams | (not directly mapped — REST doesn't natively model durable streams) |
The bridge:
- Walks
system.catalog.searchto enumerate live mounts and ops. - Generates an OpenAPI 3.x document where each
acceptsentry becomes aPOSToperation with a typed request body and response schema. - Serves the OpenAPI document at a well-known URL (e.g.,
/api/openapi.jsonor per-mount). - Translates inbound
POST /api/<root>/<mount>/<op>requests into actor invocations, returning the response as JSON.
Auto-generated docs
The vision (MASTER-PLAN.md Part 5):
"Forms from schemas. Live events from subscriptions. Live state from KV. Children as navigation. Export buttons for every protocol format. Generated entirely from
\$introspect. The actor author wrote zero UI code."
"Resolution priority: 1. Actor has custom webapp → render it. 2. Actor has typed schemas → auto-generated CQRS view. 3. Actor has nothing → raw introspect JSON. No dead ends. Every actor is browsable."
The OpenAPI spec is one of the export formats. From an OpenAPI doc you can generate typed clients in any language, hit the actor with curl, or plug it into Postman.
Schema generation
accepts.<op>.schema and accepts.<op>.returns map to JSON Schema:
typescript
// Matrix declaration
static accepts = {
'market.quote': {
description: 'Get the current price...',
schema: { symbol: { type: 'string', description: '...' } },
returns: {
price: { type: 'number', description: '...' },
volume: { type: 'number', description: '...' },
},
},
};Becomes:
yaml
# OpenAPI 3.x
paths:
/api/COM.NIMBLETEC.RICHARD-SANTOMAURO/market.feed/market.quote:
post:
summary: Get the current price...
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
symbol:
type: string
description: ...
required: [symbol]
responses:
"200":
content:
application/json:
schema:
type: object
properties:
price: { type: number, description: ... }
volume: { type: number, description: ... }The translation is mechanical because FieldDescriptor is structurally close to JSON Schema.
Authentication
Inbound REST requests carry the standard auth artifacts:
| Mechanism | How it works |
|---|---|
mx_session cookie | Same-origin requests; cookie carries the session |
Authorization: Bearer <jwt> | Cross-origin or programmatic clients |
| Per-tenant API key | Server-to-server calls (target — not implemented) |
The bridge validates the auth artifact through auth.session.validate or equivalent, derives the principal, and authorizes the actor call under that principal.
Error mapping
Matrix { ok: false, error, statusCode? } replies map to HTTP status codes:
| Matrix reply | HTTP status |
|---|---|
{ ok: true, ... } | 200 |
{ ok: false, statusCode: 400, error } | 400 |
{ ok: false, statusCode: 404, error } | 404 |
{ ok: false, error } (no statusCode) | 500 (default for unspecified errors) |
| transport timeout | 504 Gateway Timeout |
| auth failure | 401 |
| permission denied | 403 |
The body is the actor's error payload, JSON-stringified.
Why this is not the universal default today
The HTTP gateway exists and runs in production; it does not auto-expose every accepts op. Reasons it isn't yet the default:
- Authorization model. Surfacing every internal op publicly without per-op auth review is unsafe. Each op needs an explicit "exposed via REST" declaration before it should be reachable from outside the bus.
- Schema accuracy. Many actor
acceptsdeclarations today use the short string form (name: 'string') rather than the fullFieldDescriptorform. Auto-generating a typed OpenAPI spec needs the long form everywhere. - Idempotency / rate-limiting. REST callers have different expectations from in-bus callers. Idempotency keys, rate limits, and retry policies need an HTTP-level layer above the actor call.
Per-op opt-in (target shape)
A future static rest declaration on actors:
typescript
static rest = {
'market.quote': {
method: 'POST',
path: '/api/market/quote',
auth: 'session',
rateLimit: { requestsPerMinute: 60 },
},
};This makes opting an op into the REST surface explicit and reviewable.
See also
- HTTP gateway — the today-shipping HTTP surface.
- MCP — sibling LLM-native adapter.
- gRPC bridge — sibling typed-RPC adapter.
ACTOR-COMMUNICATION-CONTRACT.md— full declaration spec.