API v1 · stable

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.

i
The model proposes, the executor disposes

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.

Base URL https://api.fibric.io/v1

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.

Authorization header
curl https://api.fibric.io/v1/operators \
  -H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9"
!
Keys are tenant-scoped

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.

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.

HeaderMeaning
RateLimit-LimitRequests allowed in the current window for this route class.
RateLimit-RemainingRequests left in the current window.
RateLimit-ResetSeconds until the window resets.
Retry-AfterOn 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 body
{
  "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"
  }
}
StatusCodeWhen it happens
400invalid_requestMalformed JSON or a field failed validation. The message names the field.
401unauthenticatedMissing, malformed, or revoked API key.
403insufficient_scopeThe key is valid but lacks the scope this route requires.
404not_foundNo such resource for this tenant. Cross-tenant ids always read as not_found.
409conflictState precondition failed, for example approving an already-executed plan, or a single-flight collision.
422policy_blockedThe action was vetted and refused by a fail-closed policy. The body carries the trust decision.
429rate_limitedRate limit exceeded. Honor Retry-After.
500internal_errorSomething failed on our side. Safe to retry idempotent calls with backoff.
!
Idempotency on writes

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.

list envelope
{
  "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.

GET

List operators

GET/v1/operatorsscope operators:read

Returns operators for the authenticated tenant, newest first. Optionally filter by status.

statusstring · query

Filter by lifecycle state: active, paused, or draft. Omit to return all.

limitinteger · query

Page size, 1–100. Defaults to 20.

cursorstring · query

Pagination cursor from a previous response's next_cursor.

curl
curl https://api.fibric.io/v1/operators?status=active \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "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
}
POST

Create an operator

POST/v1/operatorsscope operators:write

Registers 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.

namerequiredstring · body

Unique, slug-style name within the tenant (for example ship-risk).

capabilitiesrequiredstring[] · body

Capabilities the operator may propose, by intent not by vendor (for example orders.hold). Which connector fulfills each capability is configuration.

policy_idrequiredstring · body

The policy that disposes of this operator's proposed actions. A policy is required, there is no unguarded operator.

modelstring · body

Base model the operator reasons with. Defaults to the tenant's configured router.

schedulestring · body

Optional cron expression to run the operator on a cadence. Omit for event-driven only.

curl
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 * * * *"
  }'
201 Created Response
json
{
  "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"
}
GET

Retrieve an operator

GET/v1/operators/{operator_id}scope operators:read

Fetches a single operator by id, including its bound policy and last run summary.

operator_idrequiredstring · path

The operator id, for example op_8f2a1c.

curl
curl https://api.fibric.io/v1/operators/op_8f2a1c \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "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.

GET

List connectors

GET/v1/connectorsscope connectors:read

Lists installed connectors for the tenant and the capabilities each one fulfills.

capabilitystring · query

Return only connectors that fulfill this capability (for example orders.hold).

kindstring · query

Filter by saas, hardware, or operator.

curl
curl https://api.fibric.io/v1/connectors?capability=orders.hold \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "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
}
GET

Retrieve a connector

GET/v1/connectors/{connector_id}scope connectors:read

Returns a single connector with its capabilities, the tools it exposes over MCP, and its health.

connector_idrequiredstring · path

The connector id, for example cn_magento.

curl
curl https://api.fibric.io/v1/connectors/cn_magento \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "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.

POST

Ingest an event

POST/v1/eventsscope events:write

Accepts 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.

typerequiredstring · body

Dotted event type, for example order.updated or sensor.reading.

entity_keyrequiredstring · body

Stable key for the thing the event is about (for example order:SO-10884). Single-flight is enforced per entity key.

idempotency_keyrequiredstring · body

Caller-supplied dedupe key. Re-sending the same key returns the original envelope rather than ingesting twice.

occurred_atstring · body

RFC 3339 timestamp of when the event happened at the source. Defaults to receipt time.

datarequiredobject · body

The event payload. Real data only, a placeholder can never pass as a metric.

curl
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 }
  }'
202 Accepted Response
json · stored envelope
{
  "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
}
i
Deduplicated, not rejected

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.

ALLOW · runs ALERT · runs, flags a human BLOCK · refused, fail-closed DEDUP · already applied
GET

List execution plans

GET/v1/plansscope plans:read

Lists plans for the tenant. Filter to proposed to find plans awaiting approval.

statusstring · query

One of proposed, approved, executed, or blocked.

operator_idstring · query

Return only plans proposed by this operator.

curl
curl https://api.fibric.io/v1/plans?status=proposed \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "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
}
GET

Retrieve a proposed plan

GET/v1/plans/{plan_id}scope plans:read

Returns 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.

plan_idrequiredstring · path

The plan id, for example pl_7c1a.

curl
curl https://api.fibric.io/v1/plans/pl_7c1a \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json · ExecutionPlan
{
  "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"
}
POST

Approve and execute a plan

POST/v1/plans/{plan_id}/approvescope plans:approve

Asks 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.

plan_idrequiredstring · path

The plan to approve, for example pl_7c1a. Must be in proposed state, otherwise 409.

approverstring · body

Optional identifier of the human or service approving. Recorded on every resulting receipt.

only_actionsstring[] · body

Optional subset of action idempotency_keys to run. Omit to run the whole plan.

curl
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" }'
200 OK Response
json · disposed
{
  "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"
}
!
A blocked action stays blocked

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.

GET

List receipts

GET/v1/receiptsscope receipts:read

Lists receipts for the tenant, newest first. Filter by operator, entity, or decision to audit a slice.

operator_idstring · query

Only receipts produced by this operator.

entity_keystring · query

Only receipts touching this entity (for example order:SO-10884).

decisionstring · query

Filter by trust decision: ALLOW, ALERT, BLOCK, or DEDUP.

curl
curl https://api.fibric.io/v1/receipts?operator_id=op_8f2a1c&decision=ALLOW \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "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
}
GET

Retrieve a receipt

GET/v1/receipts/{receipt_id}scope receipts:read

Returns 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.

receipt_idrequiredstring · path

The receipt id, for example rc_5b21.

curl
curl https://api.fibric.io/v1/receipts/rc_5b21 \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json · receipt
{
  "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.

POST

Create a webhook

POST/v1/webhooksscope webhooks:write

Registers 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.

urlrequiredstring · body

HTTPS endpoint that receives POST deliveries.

eventsrequiredstring[] · body

Event types to subscribe to: plan.proposed, plan.executed, action.disposed, receipt.created.

descriptionstring · body

Human-readable label for the subscription.

curl
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"
  }'
201 Created Response
json
{
  "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"
}
GET

List webhooks

GET/v1/webhooksscope webhooks:read

Lists the tenant's webhook subscriptions. The signing secret is never returned again after creation, rotate it from the console if it is lost.

curl
curl https://api.fibric.io/v1/webhooks \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json · delivery payload
{
  "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
}
!
Verify every delivery

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.