TypeScript SDK
@fibric/sdk is the typed client for the platform: publish envelopes, follow the stream, install and manage operators, inspect plans, and read receipts, all against the same public HTTP API every other client uses. It re-exports the kernel's types directly, so the EventEnvelope you publish and the PlannedAction you audit are the shapes documented in the event envelope and governance, with nothing lossy in between. This page is the working reference; the SDKs overview covers language support and roadmap.
Install
The client requires Node 18 or later. It is a different package from the authoring kit: install @fibric/sdk to call the platform, @fibric/connector-sdk to build things that plug into it. Most services need only one.
npm install @fibric/sdk
# a token to develop against; CI should use a workspace-scoped token instead
fibric auth login
Client initialization
The client takes a token, and the token decides everything else. A token resolves to exactly one tenant, so there is no tenant parameter to pass and no way to pass the wrong one; isolation is a property of the credential, not an argument your code gets right. Options can come from the constructor or the environment, and the constructor wins when both are set.
import { Fibric } from '@fibric/sdk';
const fibric = new Fibric({
token: process.env.FIBRIC_TOKEN!, // or set FIBRIC_TOKEN and omit
// baseUrl: 'https://api.fibric.io', // FIBRIC_BASE_URL; point at fibric dev locally
// timeoutMs: 30_000, // FIBRIC_TIMEOUT_MS
});
const me = await fibric.whoami();
// { tenant_id: 't_8f2a...', reseller_id: null, workspace: 'paperco-prod' }
| Option | Environment variable | Default | Description |
|---|---|---|---|
token | FIBRIC_TOKEN | — | Required. Mint interactively with fibric auth login, or use a workspace-scoped CI token. |
baseUrl | FIBRIC_BASE_URL | https://api.fibric.io | Point at the local kernel during development. |
timeoutMs | FIBRIC_TIMEOUT_MS | 30000 | Per-request timeout. Retries on 429 and 5xx are built in with backoff. |
A FIBRIC_TOKEN can publish events into its tenant and read that tenant's receipts. Keep it out of source, rotate it like any production credential, and prefer short-lived CI tokens over long-lived personal ones.
Working with envelopes
Everything on the platform is an EventEnvelope, and the SDK's job around events is narrow: publish envelopes in, list and follow envelopes out. The type is re-exported from the kernel, field for field.
import type { EventEnvelope, ExecutionPlan, PlannedAction } from '@fibric/sdk';
// EventEnvelope
// {
// event_id, reseller_id, tenant_id, workspace_id,
// source, event_type, correlation_id, payload,
// agent_id, session_id
// }
Publishing events
events.publish() takes the fields you own and returns the stamped envelope. The server assigns event_id, stamps reseller_id and tenant_id from the token, and generates a correlation_id if you did not supply one. Publishing never acts by itself: an event can only cause an operator to propose a plan, and the executor disposes that plan under policy.
const env = await fibric.events.publish({
source: 'warehouse-app',
event_type: 'pick.exception', // dotted noun.verb; operators match on this
payload: { lane: 'B4', order_id: 'SO-11290' },
// correlation_id: existing_id, // pass one to join an existing thread
});
console.log(env.event_id, env.correlation_id);
| Field | Type | Required | Description |
|---|---|---|---|
source | string | yes | Where this observation came from: "shopify", "bacnet-gw-7", "cron", "operator:jenny". |
event_type | string | yes | Dotted noun.verb type, for example order.created. The router matches operator triggers against it. |
payload | Record<string, unknown> | no | The observation itself. Defaults to {}. |
correlation_id | string | no | Joins this event to an existing thread of work. Generated when omitted. |
workspace_id | string | null | no | Narrows the event to one workspace within the tenant. |
agent_id, session_id | string | null | no | Set when the event is an operator's own output; leave null for external observations. |
Listing and filtering
Lists page like every other list in the SDK: an items array and an opaque cursor, plus an async iterator that drives the cursor for you. Filters combine as AND.
// one page
const page = await fibric.events.list({
event_type: 'order.*', // same glob grammar the router uses
source: 'cn-magento',
since: '2026-07-01T00:00:00Z',
limit: 200,
});
// or iterate the whole window
for await (const env of fibric.events.iterate({ event_type: 'hvac.zone.*' })) {
console.log(env.event_type, env.payload);
}
// one envelope by id
const one = await fibric.events.get('evt_01J9YV...');
Subscribing to the stream
events.subscribe() holds a streaming connection and yields envelopes as they arrive, resuming from the last delivered cursor on reconnect. Delivery is at-least-once; dedupe on event_id if your consumer is not naturally idempotent. Streaming delivery is a preview surface in v0.9: the shape below is stable, and cursor-polled iterate() is the fallback that works against every deployment today.
const sub = fibric.events.subscribe({ event_type: 'order.*' });
for await (const env of sub) {
await handle(env); // at-least-once: dedupe on env.event_id
}
// stop from another task
sub.close();
An operator does not subscribe with the SDK; it declares a trigger and the platform routes envelopes to it. subscribe() is for your own services: mirrors, dashboards, escalation hooks. See defining operators for the operator side.
Installing operators
operators.create() installs an operator, either from a marketplace pack by id or from a local definition you have pushed. Installing from a pack binds each declared capability to one of the tenant's connectors and applies the guardrail defaults you accept, exactly as the CLI flow does interactively. New installs default to propose-only mode: the operator senses and reasons, plans appear in the queue, and the executor disposes nothing until you flip the mode.
const op = await fibric.operators.create({
from: 'op-order-sentinel', // marketplace pack id
name: 'order-risk',
bindings: { // capability -> connection
'commerce.orders': 'magento-live',
'support.conversations': 'kustomer-live',
'shipping.scans': 'ships-live',
},
acceptGuardrailDefaults: true, // the pack's recommended TrustPolicy[]
mode: 'propose-only', // default; 'live' is an explicit later step
});
// manage the lifecycle
await fibric.operators.pause(op.id);
await fibric.operators.resume(op.id);
const all = await fibric.operators.list();
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Pack id (op-order-sentinel) or a pushed local definition. |
name | string | yes | The operator's name within the tenant. Unique per workspace. |
bindings | Record<string, string> | for packs | Maps each capability the pack requires to a connection. Creation fails listing the unbound capabilities if incomplete. |
acceptGuardrailDefaults | boolean | no | Apply the pack's recommended TrustPolicy[]. Defaults to false; the tenant policy always has the last word either way. |
mode | 'propose-only' | 'live' | no | Defaults to propose-only. There is no mode that bypasses the trust policy. |
Reading receipts
Every disposed action leaves a receipt, including refusals: a BLOCK is a receipt too, not an error you lost. The receipt carries the action as proposed, the executor's disposition, and the outcome, all joined to the originating envelope by correlation_id.
// everything that happened because of one envelope
const receipts = await fibric.receipts.list({ correlation_id: env.correlation_id });
for (const r of receipts.items) {
// decision: 'ALLOW' | 'BLOCK' | 'ALERT' | 'DEDUP'
console.log(r.action.connector, r.action.tool, r.decision, r.ok);
}
// or stream a time window without managing cursors
for await (const r of fibric.receipts.iterate({ since: '2026-06-01' })) {
if (r.decision === 'BLOCK') audit(r);
}
| Receipt field | Type | Description |
|---|---|---|
action | PlannedAction | The action exactly as proposed: connector, tool, args, value, entity_key, idempotency_key. |
decision | 'ALLOW' | 'BLOCK' | 'ALERT' | 'DEDUP' | The executor's disposition: the kernel's TrustDecision plus DEDUP for a side effect collapsed into an earlier identical one. |
ok | boolean | Whether the handler succeeded. A BLOCK is ok: false with no handler call at all. |
error | string | undefined | The failure or refusal reason when ok is false. |
correlation_id | string | Joins the receipt to its envelope, plan, and sibling actions. |
Plans and dispositions
Between the envelope and the receipt sits the ExecutionPlan: what an operator proposed, before the executor disposed it. The SDK reads the plan queue, and for plans held for review, approves or vetoes them. This is the surface a human-in-the-loop tool builds on.
// plans awaiting review (e.g. from operators in propose-only mode)
const pending = await fibric.plans.list({ status: 'pending' });
for (const plan of pending.items) {
console.log(plan.reasoning); // the operator's stated rationale
for (const a of plan.actions) {
console.log(' ', a.tool, JSON.stringify(a.args));
}
}
await fibric.plans.approve(pending.items[0].id);
await fibric.plans.veto(pending.items[1].id, { reason: 'wrong order scope' });
Approval is consent, not command: an approved plan still passes the trust policy action by action, and an action no policy allows is still blocked. The actions & plans API documents the underlying endpoints.
Error handling
The client throws FibricError carrying the HTTP status, a stable error code, and the request id. Blocked actions are not thrown errors: the submission succeeded, the executor disposed fail-closed, and the dispositions are in the result. Inspect decision per action instead of wrapping submissions in try/catch and hoping.
import { FibricError } from '@fibric/sdk';
try {
const result = await fibric.plans.submit(plan);
for (const r of result.actions) {
if (r.decision === 'BLOCK') console.warn('fail-closed:', r.action.tool, r.error);
}
} catch (e) {
if (e instanceof FibricError) {
// e.status (HTTP), e.code (stable string), e.requestId (for support)
if (e.status === 429) { /* backoff is built in; this is the ceiling */ }
}
throw e;
}
The full code table is on the errors page; throughput ceilings on rate limits & quotas.
Keep going
- Example projects: the SDK in complete, runnable context.
- API overview: the HTTP surface this client wraps.
- Connector SDK: the authoring kit, if you are building rather than calling.
- Local development: pointing the client at the local kernel.