Overview
A running QiForge oracle exposes a REST + WebSocket surface. Most routes require a UCAN delegation header; some (/health, /docs, plus anything declared in getAuthExcludedRoutes() or authExcludedRoutes) are public.
The Swagger UI at /docs is generated from the running app's controllers — open it in a browser for the live, deployment-specific reference.
Authentication
Protected routes authenticate with a user-signed UCAN invocation, plus an optional delegation for downstream authorization. This is the same model the Identity and auth guide describes.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <invocation> | Yes (primary) | The user-signed UCAN invocation — proves who is calling. Only read when X-Auth-Type: ucan is also present. |
X-Auth-Type: ucan | Yes (primary) | Selector telling the middleware to read the bearer as a UCAN invocation. Without it the bearer is ignored. |
x-ucan-delegation | Fallback / downstream-authz | The user→oracle delegation. Carries the capabilities plugins use to mint downstream invocations. Also accepted as the auth artifact on its own for clients that haven't migrated to invocation auth. |
x-did | No | The user's IXO DID (informational). The runtime derives the authenticated DID from the invocation/delegation — x-did is not used for authentication. |
x-matrix-access-token | No | Matrix session token (used by Portal/Slack). |
x-matrix-homeserver | No | Matrix homeserver URL. |
x-timezone | No | Propagates to rtCtx.user.timezone. |
x-request-id | No | Correlation ID; echoed back as X-Request-Id. |
When neither an invocation nor a delegation is present, protected routes return 401:
Missing UCAN authentication: provide Authorization: Bearer <invocation> with X-Auth-Type: ucan, or an x-ucan-delegation headerA malformed or expired invocation returns 401 Invalid UCAN invocation. Public routes ignore auth headers and don't validate them.
CORS
CORS_ORIGIN controls allowed origins (wildcard * is the default; specific origins enable credentials).
Allowed request headers:
Content-Type, Authorization, x-ucan-delegation,
x-matrix-access-token, x-matrix-homeserver,
x-did, x-request-id, x-auth-type, x-timezoneAllowed methods: GET, POST, PUT, DELETE, PATCH, OPTIONS.
Exposed response headers: X-Request-Id.
Public routes
| Route | Method | Auth | Purpose |
|---|---|---|---|
/ | GET | Public | Landing JSON ({ status, message, timestamp }). |
/health | GET | Public | Liveness probe ({ status, timestamp }). |
/docs | GET | Public | Swagger UI. |
/docs/(.*) | GET | Public | Swagger static assets. |
Plus any route returned by a plugin's getAuthExcludedRoutes() or by createOracleApp({ authExcludedRoutes }).
Protected routes
All of these require the auth headers above. For the request/response schemas of your specific deployment, hit GET /docs.
Sessions (/sessions)
| Route | Method | Purpose |
|---|---|---|
/sessions | POST | Create a chat session. Returns the new sessionId. |
/sessions?limit&offset | GET | List the authenticated user's sessions (paginated; defaults limit=20, offset=0). |
/sessions/:sessionId | DELETE | Delete a session. |
Messages (/messages)
| Route | Method | Purpose |
|---|---|---|
/messages/:sessionId | POST | Send a message. Set stream: true in the body for an SSE stream instead of a single JSON reply. |
/messages/:sessionId | GET | List the messages in a session. |
/messages/abort | POST | Abort an in-flight stream. Body: { "sessionId": "..." }. |
POST /messages/:sessionId body (SendMessageDto):
| Field | Type | Notes |
|---|---|---|
message | string | Required. The user's message text. |
stream | boolean | true → SSE stream; otherwise a single JSON reply. Default false. |
returnAllMessages | boolean | Non-stream only: also return the full transcript. Testing aid. Default false. |
tools | array | Browser-tool declarations ({ name, schema, description }). |
agActions | array | AG-UI action declarations ({ name, description, schema, hasRender? }). |
attachments | array | Up to 10 Matrix attachments (mxcUri or eventId, plus filename, mimetype). |
mcpInvocations | object | Map of MCP tool name → base64 CAR invocation for protected MCP tools. |
timezone | string | Overrides rtCtx.user.timezone. |
homeServer | string | The user's Matrix homeserver. |
metadata | object | Free-form (editorRoomId, spaceId, …). |
Streaming is a flag on this single endpoint — there is no separate /stream route.
Delegation (/delegation)
A user→oracle UCAN delegation lets the oracle mint downstream-service invocations on the user's behalf. The client-sdk re-auth popup POSTs here; a non-React client must implement this to complete the authorization flow.
| Route | Method | Purpose |
|---|---|---|
/delegation | POST | Store a freshly-signed delegation. Returns { ok: true, expiration? }. |
/delegation | GET | Authorization status: { authorized: boolean, expiration? }. |
/delegation | DELETE | Revoke the stored delegation. Returns { ok: true }. |
POST /delegation body (StoreDelegationDto):
| Field | Type | Notes |
|---|---|---|
raw | string | Required. Base64-encoded UCAN delegation CAR (user → oracle). |
issuer | string | Delegation issuer DID (the user). |
audience | string | Delegation audience DID (this oracle). |
expiration | number | Unix timestamp, seconds. |
Other modules
SubscriptionModule— credit/subscription gating middleware (active when thecreditsplugin is loaded).WsModule— the WebSocket gateway (see below).
Plugins contribute more routes via getNestModules() — e.g. SlackModule (Slack webhooks), UserPreferencesController (/user-preferences), and claim-processing cron endpoints. The Swagger UI at /docs is generated from your app's controllers — including plugin and host routes — so it's always the authoritative, deployment-specific list.
WebSocket
The oracle runs a socket.io gateway on namespace /. Plugins emit typed events via rtCtx.emit; the framework forwards them to the client over the session's room.
Handshake
Connect with a sessionId query param and a UCAN in auth. A handshake with no sessionId, or with no valid UCAN, is disconnected immediately.
import { io } from 'socket.io-client';
const socket = io('https://your-oracle.example', {
query: { sessionId },
auth: {
invocation, // primary: the user-signed UCAN invocation
ucanDelegation, // fallback: a bare delegation (pre-invocation clients)
},
});The server validates the invocation (primary) or delegation (fallback) the same way HTTP requests do; the authenticated DID is the validated invoker, never the query value. On success it emits connected.
Server → client events
| Event | When it fires |
|---|---|
connected | Handshake accepted. |
pong | Reply to a ping. |
status | Reply to a status request (connection stats). |
subscribed | Subscription acknowledged. |
available-events | Reply to list-events — the full event catalog. |
tool_call | A tool is being invoked (or has returned). |
action_call | An AG-UI action is being invoked. |
render_component | A render-component result is ready for the client. |
browser_tool_call | A browser-side tool is being invoked on the user's tab. |
router_update | A routing decision between agents/sub-agents. |
message_cache_invalidation | A cached message should be dropped. |
Client → server events
| Event | Purpose |
|---|---|
ping | Keep-alive; server replies pong. |
status | Request connection stats; server replies status. |
subscribe | Subscribe to session events. |
list-events | Ask for the event catalog; server replies available-events. |
tool_result | Return a browser-tool result ({ toolCallId, result, error? }). |
action_call_result | Return an AG-UI action result ({ sessionId, toolCallId, result }). |
Event payload types are currently Record<string, unknown> and may be tightened in future runtime versions. The @ixo/oracles-client-sdk React SDK handles this handshake, parses these events, and renders them.
Client SDK
For frontend integration, use @ixo/oracles-client-sdk — it handles the SSE / WebSocket protocol, UCAN delegation, and event parsing. See Client SDK.
Related references
- Identity and auth — the UCAN flow.
- Plugin HTTP endpoints — adding your own routes.
- Client SDK — React integration.