API reference
The Fibric REST API is how code drives the operational loop: ingest what your systems sense, read and approve the execution plans the model proposes, run operators, and read the receipt for every action a deterministic executor disposes. Everything is governed by default, real data only, every action checked against a fail-closed policy, every tenant provably walled off.
No API call makes the model act on its own. You ingest events, the model returns a validated ExecutionPlan, and the only way actions run is when a deterministic executor checks every one against your policy. Approving a plan asks for execution, it does not bypass the guardrail.
Base URL
All requests are made over HTTPS to the regional API host. The version is pinned in the path so an upgrade is never silent.
Requests and responses are JSON. Send Content-Type: application/json on any request with a body. Timestamps are RFC 3339 in UTC (for example 2026-06-14T09:02:11Z). Identifiers are opaque, prefixed strings, never assume a format beyond the prefix (op_ operator, cn_ connector, ev_ event, pl_ plan, run_ run, rc_ receipt, whk_ webhook).
Authentication
Authenticate every request with a secret API key in the Authorization header as a Bearer token. Keys are issued per tenant in the Fibric console and carry a fixed set of scopes. Treat a key like a password: it is shown once, it is never logged in a receipt, and it identifies exactly one tenant, so the tenancy wall is enforced by the key, not by anything you pass in the body.
curl https://api.fibric.io/v1/operators \
-H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9"
A key belongs to one tenant and can only read or write that tenant's data. There is no cross-tenant key, no admin override in the public API, and no way to set reseller_id or tenant_id in a body to reach another tenant, the server derives both from the key and rejects any mismatch.
Versioning
The major version lives in the URL (/v1). Within a major version, changes are additive only: new fields, new endpoints, new enum members. We never remove a field or change a type inside a major version. A breaking change ships as a new major (/v2), and the previous major is supported for at least twelve months after the successor is stable.
- Optionally pin a dated revision with the
Fibric-Versionheader (for example2026-06-01) to freeze additive-but-default-changing behavior. - Treat unknown JSON fields as forward-compatible, parse leniently and ignore what you do not recognize.
Rate limits
Limits are per tenant and per route class. Reads are generous; writes and event ingestion are metered to protect the executor. Every response carries the current budget so you can back off before you are throttled.
| Header | Meaning |
|---|---|
RateLimit-Limit | Requests allowed in the current window for this route class. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window resets. |
Retry-After | On a 429, seconds to wait before retrying. |
Default ceilings: 1000 reads/min, 120 writes/min, and 600 events/min per tenant. Exceeding a limit returns 429 Too Many Requests; retry with exponential backoff and respect Retry-After. Event ingestion is idempotent (see below), so a retried event is deduplicated rather than double-counted.
Errors
Errors use standard HTTP status codes and a consistent JSON body. The code is a stable, machine-readable string; message is human-readable and may change; request_id identifies the request in your receipts and our logs.
{
"error": {
"code": "plan_not_approvable",
"message": "Plan pl_7c1a is already executed and cannot be approved.",
"request_id": "req_9b3e21f0",
"docs": "https://api.fibric.io/docs/errors#plan_not_approvable"
}
}
| Status | Code | When it happens |
|---|---|---|
400 | invalid_request | Malformed JSON or a field failed validation. The message names the field. |
401 | unauthenticated | Missing, malformed, or revoked API key. |
403 | insufficient_scope | The key is valid but lacks the scope this route requires. |
404 | not_found | No such resource for this tenant. Cross-tenant ids always read as not_found. |
409 | conflict | State precondition failed, for example approving an already-executed plan, or a single-flight collision. |
422 | policy_blocked | The action was vetted and refused by a fail-closed policy. The body carries the trust decision. |
429 | rate_limited | Rate limit exceeded. Honor Retry-After. |
500 | internal_error | Something failed on our side. Safe to retry idempotent calls with backoff. |
Send an Idempotency-Key header on any POST that creates or runs something. Retrying with the same key returns the original result instead of acting twice. Event ingestion and action execution carry their own idempotency keys in the body, which is what makes a runaway flood structurally impossible.
Endpoints at a glance
The API follows the loop: ingest events, the model proposes plans, you approve, the executor disposes, and you read receipts.
Pagination
List endpoints are cursor-paginated. Pass limit (1–100, default 20) and follow next_cursor until it is null. Lists return newest first.
{
"object": "list",
"data": [ /* resources */ ],
"has_more": true,
"next_cursor": "cur_eyJpZCI6Im9wXzhmMmEifQ"
}
Operators
An operator is a named AI worker that runs an operation. It senses through connectors, reasons with a base model, and proposes plans. Creating an operator does not act, it registers the worker, its capabilities, and the policy that disposes of whatever it proposes.
List operators
operators:readReturns operators for the authenticated tenant, newest first. Optionally filter by status.
Filter by lifecycle state: active, paused, or draft. Omit to return all.
Page size, 1–100. Defaults to 20.
Pagination cursor from a previous response's next_cursor.
curl https://api.fibric.io/v1/operators?status=active \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "op_8f2a1c",
"object": "operator",
"name": "ship-risk",
"status": "active",
"capabilities": ["orders.hold", "orders.notify"],
"policy_id": "pol_22b9",
"created_at": "2026-06-10T14:21:03Z"
}
],
"has_more": false,
"next_cursor": null
}
Create an operator
operators:writeRegisters a new operator and binds it to a fail-closed policy. The operator can sense and propose immediately; nothing it proposes runs until the executor disposes of it against this policy.
Unique, slug-style name within the tenant (for example ship-risk).
Capabilities the operator may propose, by intent not by vendor (for example orders.hold). Which connector fulfills each capability is configuration.
The policy that disposes of this operator's proposed actions. A policy is required, there is no unguarded operator.
Base model the operator reasons with. Defaults to the tenant's configured router.
Optional cron expression to run the operator on a cadence. Omit for event-driven only.
curl -X POST https://api.fibric.io/v1/operators \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: op-create-7c1a" \
-d '{
"name": "ship-risk",
"capabilities": ["orders.hold", "orders.notify"],
"policy_id": "pol_22b9",
"schedule": "*/15 * * * *"
}'
{
"id": "op_8f2a1c",
"object": "operator",
"name": "ship-risk",
"status": "active",
"capabilities": ["orders.hold", "orders.notify"],
"policy_id": "pol_22b9",
"schedule": "*/15 * * * *",
"created_at": "2026-06-14T09:01:00Z"
}
Retrieve an operator
operators:readFetches a single operator by id, including its bound policy and last run summary.
The operator id, for example op_8f2a1c.
curl https://api.fibric.io/v1/operators/op_8f2a1c \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"id": "op_8f2a1c",
"object": "operator",
"name": "ship-risk",
"status": "active",
"capabilities": ["orders.hold", "orders.notify"],
"policy_id": "pol_22b9",
"last_run": {
"run_id": "run_5b21",
"sensed": 42,
"proposed": 3,
"applied": 3,
"at": "2026-06-14T09:02:11Z"
}
}
Connectors
A connector plugs Fibric into a system you already run, software like a help desk or order system, or hardware like sensors and locks. Every connector is built on MCP and advertises capabilities by intent, so capability over connector means swapping one vendor for another is configuration, not a rewrite.
List connectors
connectors:readLists installed connectors for the tenant and the capabilities each one fulfills.
Return only connectors that fulfill this capability (for example orders.hold).
Filter by saas, hardware, or operator.
curl https://api.fibric.io/v1/connectors?capability=orders.hold \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "cn_magento",
"object": "connector",
"name": "Magento Orders",
"kind": "saas",
"transport": "mcp",
"capabilities": ["orders.read", "orders.hold", "orders.notify"],
"status": "connected"
}
],
"has_more": false,
"next_cursor": null
}
Retrieve a connector
connectors:readReturns a single connector with its capabilities, the tools it exposes over MCP, and its health.
The connector id, for example cn_magento.
curl https://api.fibric.io/v1/connectors/cn_magento \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"id": "cn_magento",
"object": "connector",
"name": "Magento Orders",
"kind": "saas",
"transport": "mcp",
"capabilities": ["orders.read", "orders.hold", "orders.notify"],
"tools": [
{ "name": "orders.read", "side_effect": false },
{ "name": "orders.hold", "side_effect": true }
],
"status": "connected",
"last_synced_at": "2026-06-14T08:59:40Z"
}
Events
Events are how systems sense into Fibric. Every event is normalized to one canonical EventEnvelope carrying reseller_id and tenant_id, so isolation is on the row, not on a convention. Ingestion is idempotent: the same idempotency_key is accepted once and deduplicated thereafter.
Ingest an event
events:writeAccepts one event envelope. The server stamps reseller_id and tenant_id from your key, you cannot set them to reach another tenant. The event is routed to any operator whose capabilities subscribe to its type.
Dotted event type, for example order.updated or sensor.reading.
Stable key for the thing the event is about (for example order:SO-10884). Single-flight is enforced per entity key.
Caller-supplied dedupe key. Re-sending the same key returns the original envelope rather than ingesting twice.
RFC 3339 timestamp of when the event happened at the source. Defaults to receipt time.
The event payload. Real data only, a placeholder can never pass as a metric.
curl -X POST https://api.fibric.io/v1/events \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "order.updated",
"entity_key": "order:SO-10884",
"idempotency_key": "magento:SO-10884:v7",
"occurred_at": "2026-06-14T08:58:00Z",
"data": { "status": "open", "ship_by": "2026-06-15", "carrier_scanned": false }
}'
{
"id": "ev_3a91c7",
"object": "event",
"reseller_id": "rs_fibric",
"tenant_id": "t_8f2a…c901",
"type": "order.updated",
"entity_key": "order:SO-10884",
"idempotency_key": "magento:SO-10884:v7",
"occurred_at": "2026-06-14T08:58:00Z",
"received_at": "2026-06-14T08:58:01Z",
"dedup": false
}
Re-sending an event with an idempotency_key already seen returns 200 with "dedup": true and the original envelope. The flood is absorbed, not multiplied, which is what makes the 657-message scenario structurally impossible.
Actions & execution plans
An ExecutionPlan is what the model proposes after it reasons over what it sensed. It is a validated list of actions[], each carrying an entity_key and an idempotency_key. The model never executes a plan itself. A deterministic executor disposes of it, evaluating every action against your fail-closed policy and producing a trust decision before anything runs.
List execution plans
plans:readLists plans for the tenant. Filter to proposed to find plans awaiting approval.
One of proposed, approved, executed, or blocked.
Return only plans proposed by this operator.
curl https://api.fibric.io/v1/plans?status=proposed \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "pl_7c1a",
"object": "execution_plan",
"operator_id": "op_8f2a1c",
"status": "proposed",
"action_count": 3,
"proposed_at": "2026-06-14T09:02:09Z"
}
],
"has_more": false,
"next_cursor": null
}
Retrieve a proposed plan
plans:readReturns the full plan, including each proposed action with its entity_key, idempotency_key, and the trust decision the executor would apply. Inspect this before approving.
The plan id, for example pl_7c1a.
curl https://api.fibric.io/v1/plans/pl_7c1a \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"id": "pl_7c1a",
"object": "execution_plan",
"operator_id": "op_8f2a1c",
"proposed_by": "model",
"status": "proposed",
"actions": [
{
"capability": "orders.hold",
"entity_key": "order:SO-10884",
"idempotency_key": "ship-risk:SO-10884:hold",
"args": { "reason": "will miss ship-by date" },
"decision": "ALLOW"
},
{
"capability": "orders.notify",
"entity_key": "order:SO-10884",
"idempotency_key": "ship-risk:SO-10884:notify",
"args": { "to": "owner" },
"decision": "ALERT"
}
],
"policy_id": "pol_22b9",
"proposed_at": "2026-06-14T09:02:09Z"
}
Approve and execute a plan
plans:approveAsks the executor to dispose of the plan. Approval is a request to run, not a bypass: the executor still evaluates every action against the policy, enforces single-flight per entity_key, dedupes on each idempotency_key, and emits a receipt per action. An action the policy blocks stays blocked even with approval.
The plan to approve, for example pl_7c1a. Must be in proposed state, otherwise 409.
Optional identifier of the human or service approving. Recorded on every resulting receipt.
Optional subset of action idempotency_keys to run. Omit to run the whole plan.
curl -X POST https://api.fibric.io/v1/plans/pl_7c1a/approve \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: approve-pl_7c1a" \
-d '{ "approver": "ops@acme.com" }'
{
"id": "pl_7c1a",
"object": "execution_plan",
"status": "executed",
"approver": "ops@acme.com",
"results": [
{ "idempotency_key": "ship-risk:SO-10884:hold", "decision": "ALLOW", "outcome": "applied", "receipt_id": "rc_5b21" },
{ "idempotency_key": "ship-risk:SO-10884:notify", "decision": "ALERT", "outcome": "applied", "receipt_id": "rc_5b22" }
],
"executed_at": "2026-06-14T09:02:11Z"
}
If the policy returns BLOCK for an action, approval does not override it. The action's result is "outcome": "refused" with the policy rule that vetoed it, and a receipt still records the refusal. Fail-closed means the absence of an allow is a deny.
Receipts
A receipt is the immutable record of one disposed action: the envelope that triggered it, the proposed action, the policy decision, the idempotency key, and the outcome. Receipts are why everything Fibric did is logged and explainable after the fact. They are append-only and cannot be edited or deleted.
List receipts
receipts:readLists receipts for the tenant, newest first. Filter by operator, entity, or decision to audit a slice.
Only receipts produced by this operator.
Only receipts touching this entity (for example order:SO-10884).
Filter by trust decision: ALLOW, ALERT, BLOCK, or DEDUP.
curl https://api.fibric.io/v1/receipts?operator_id=op_8f2a1c&decision=ALLOW \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "rc_5b21",
"object": "receipt",
"operator": "ship-risk",
"capability": "orders.hold",
"entity_key": "order:SO-10884",
"decision": "ALLOW",
"outcome": "applied",
"at": "2026-06-14T09:02:11Z"
}
],
"has_more": false,
"next_cursor": null
}
Retrieve a receipt
receipts:readReturns the full record for one action: the triggering envelope, the proposed action, the policy evaluation, the idempotency key that guarantees the action is never applied twice, and the outcome.
The receipt id, for example rc_5b21.
curl https://api.fibric.io/v1/receipts/rc_5b21 \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"id": "rc_5b21",
"object": "receipt",
"reseller_id": "rs_fibric",
"tenant_id": "t_8f2a…c901",
"operator": "ship-risk",
"capability": "orders.hold",
"entity_key": "order:SO-10884",
"triggered_by_event": "ev_3a91c7",
"plan_id": "pl_7c1a",
"proposed_by": "model",
"policy": { "decision": "ALLOW", "rule": "orders.hold" },
"idempotency_key": "ship-risk:SO-10884:hold",
"outcome": "applied",
"approver": "ops@acme.com",
"at": "2026-06-14T09:02:11Z"
}
Webhooks
Webhooks push events to your endpoint instead of you polling. Subscribe to the moments that matter, a plan was proposed, an action was disposed, a receipt was written, and verify each delivery with the signature header. Subscriptions are scoped to the tenant of the key that created them.
Create a webhook
webhooks:writeRegisters an endpoint and the event types to deliver. The response includes a signing secret, shown once, used to verify the Fibric-Signature header on every delivery.
HTTPS endpoint that receives POST deliveries.
Event types to subscribe to: plan.proposed, plan.executed, action.disposed, receipt.created.
Human-readable label for the subscription.
curl -X POST https://api.fibric.io/v1/webhooks \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://acme.com/hooks/fibric",
"events": ["plan.proposed", "receipt.created"],
"description": "Route at-risk holds into our ops channel"
}'
{
"id": "whk_d41c",
"object": "webhook",
"url": "https://acme.com/hooks/fibric",
"events": ["plan.proposed", "receipt.created"],
"signing_secret": "whsec_2b7f…shown_once",
"status": "enabled",
"created_at": "2026-06-14T09:05:00Z"
}
List webhooks
webhooks:readLists the tenant's webhook subscriptions. The signing secret is never returned again after creation, rotate it from the console if it is lost.
curl https://api.fibric.io/v1/webhooks \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "whk_d41c",
"object": "webhook",
"url": "https://acme.com/hooks/fibric",
"events": ["plan.proposed", "receipt.created"],
"status": "enabled"
}
],
"has_more": false,
"next_cursor": null
}
Each delivery carries Fibric-Signature: t=<ts>,v1=<hex>, an HMAC-SHA256 of <ts>.<body> using your signing secret. Recompute it and reject anything that does not match or whose timestamp is stale. Deliveries retry with backoff for up to 24 hours.