These docs describe the target Fibric platform contract and proposed developer experience. Public Fibric CLI, API endpoints, and SDK packages are not yet available. All present-tense API, CLI, SDK, tenancy, security, retention, retry, and pricing language below is normative target language, not a statement that the surface is callable or commercially available today. Commands, package names, URLs, responses, limits, and timelines are reference examples unless a page explicitly identifies a deployed BearScope path.
TypeScript SDK
@fibric/sdk is a proposed TypeScript client for the target platform contract. It is not publicly installable and the referenced HTTP API is not callable today. The examples below are non-runnable design shapes, not a working client reference.
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 v1.0: 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.