Fibric. Docs fibric.io →
v0.9 · preview
Reference

Actions & plans API

An ExecutionPlan is what the model proposes; the deterministic executor is what disposes it. This page documents reading proposed plans with the trust verdict on every action, approving or vetoing a plan, listing disposed actions, and undoing a reversible one. The model never executes anything through this API, and neither do you: every side effect passes through the executor's policy gate, single-flight lock, and idempotency dedup.

Shared conventions, including authentication, pagination, the Idempotency-Key header, and the error envelope, are defined in the API overview. Error codes are catalogued in Errors.

The plan object

A plan wraps the kernel's ExecutionPlan, its optional reasoning and its actions[], with API metadata and the verdict the trust policy produced for each action. Action fields match PlannedAction in packages/kernel/src/trust.ts: connector, tool, args, optional value, entity_key, and idempotency_key.

idstring

Unique identifier, prefixed pl_.

objectstring

Always execution_plan.

operator_idstring

The operator that proposed the plan.

event_idstring or null

The event that triggered the run, or null for a scheduled run.

statusstring

One of proposed, executed, vetoed, or expired. A plan whose actions all clear the policy without needing escalation executes without waiting; a plan holding an action that requires human approval sits in proposed until approved, vetoed, or expired.

reasoningstring or null

The model's stated reasoning for the plan, verbatim. Optional in the kernel contract, so it may be null.

actionsobject[]

The proposed actions, in order. Each carries an action id (prefix act_), the PlannedAction fields, and a verdict.

actions[].verdictobject

The trust evaluation: decision (ALLOW, ALERT, or BLOCK), tier (the trust tier of the tool, 0–3), and rule, the guardrail that matched, or null when the block came from the default-closed fallthrough.

proposed_atstring

RFC 3339 timestamp of the proposal.

disposed_atstring or null

When the executor finished disposing the plan. null while proposed.

expires_atstring or null

Plans awaiting approval expire after 72 hours by default and move to expired. Nothing in an expired plan ever runs.

Verdicts a plan's actions can carry, and the disposition DEDUP that appears once an action has been disposed:

ALLOW · runs ALERT · runs, flags a human BLOCK · refused, fail-closed DEDUP · already applied, skipped
json · the plan object
{
  "id": "pl_7c1a",
  "object": "execution_plan",
  "operator_id": "op_8f2a1c",
  "event_id": "ev_3a91c7",
  "status": "proposed",
  "reasoning": "SO-10884 is paid, unfulfilled, and two days from its promise with no carrier scan. Hold it and tell the owner.",
  "actions": [
    {
      "id": "act_91d0",
      "connector": "cn_7d2f4a",
      "tool": "order.hold",
      "args": { "order": "SO-10884", "reason": "will miss promised ship date" },
      "value": 184.5,
      "entity_key": "order:SO-10884",
      "idempotency_key": "order-risk:SO-10884:hold",
      "verdict": { "decision": "ALLOW", "tier": 1, "rule": "tool:order.hold max_value:500" }
    },
    {
      "id": "act_91d1",
      "connector": "cn_7d2f4a",
      "tool": "order.notify",
      "args": { "order": "SO-10884", "to": "owner" },
      "entity_key": "order:SO-10884",
      "idempotency_key": "order-risk:SO-10884:notify",
      "verdict": { "decision": "ALERT", "tier": 2, "rule": "tool:order.notify" }
    }
  ],
  "proposed_at": "2026-07-02T15:02:09Z",
  "disposed_at": null,
  "expires_at": "2026-07-05T15:02:09Z"
}
GET

List plans

GET/v1/plansscope plans:read

Returns plans for the tenant, newest first, cursor-paginated. Filter to status=proposed to build an approval queue.

statusstring · query

One of proposed, executed, vetoed, or expired.

operator_idstring · query

Only plans proposed by this operator.

entitystring · query

Only plans holding an action with this entity_key.

sincestring · query

Only plans proposed at or after this RFC 3339 timestamp.

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/plans?status=proposed&operator_id=op_8f2a1c" \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "object": "list",
  "data": [
    {
      "id": "pl_7c1a",
      "object": "execution_plan",
      "operator_id": "op_8f2a1c",
      "event_id": "ev_3a91c7",
      "status": "proposed",
      "action_count": 2,
      "proposed_at": "2026-07-02T15:02:09Z",
      "expires_at": "2026-07-05T15:02:09Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

List responses summarize each plan with action_count; retrieve a single plan for full actions and verdicts.

GET

Retrieve a plan

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

Returns the full plan object, including the model's reasoning and the verdict on every action. Inspect this before approving; the verdicts you see are the verdicts the executor will enforce.

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

Returns the plan object. A cross-tenant id reads as 404 not_found.

POST

Approve a plan

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

The human escalation path: asks the executor to run a plan that is waiting in proposed. Approval is a request to run, not a bypass. The executor re-evaluates every action against the guardrails at execution time, enforces single-flight per entity_key, dedupes on each idempotency_key, and writes a receipt per action. An action whose verdict is BLOCK stays blocked with your approval on the record.

plan_idrequiredstring · path

The plan to approve. Must be in proposed, otherwise 409 state_conflict.

approverstring · body

Identifier of the human or service approving, for example an email address. Recorded on every resulting receipt. Defaults to the API key's label.

only_actionsstring[] · body

Optional subset of action ids (act_…) to run. Actions not listed are skipped and recorded as skipped. Omit to run the whole plan.

notestring · body

Optional free-text note recorded with the approval.

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": [
    { "action_id": "act_91d0", "disposition": "ALLOW", "ok": true, "receipt_id": "rc_5b21" },
    { "action_id": "act_91d1", "disposition": "ALERT", "ok": true, "receipt_id": "rc_5b22" }
  ],
  "disposed_at": "2026-07-02T15:04:41Z"
}

Error cases:

StatusCodeWhen
404not_foundNo plan with this id exists for the tenant.
409state_conflictThe plan is already executed, vetoed, or expired.
409entity_lockedAnother plan holds the single-flight lock on an entity this plan touches. See below.
409idempotency_conflictThe Idempotency-Key header was reused with a different body.
POST

Veto a plan

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

Refuses a proposed plan. Nothing in a vetoed plan ever runs, and the veto, with who and why, is written to the audit trail. The operator sees the veto as feedback on its next run over the same entity.

plan_idrequiredstring · path

The plan to veto. Must be in proposed, otherwise 409 state_conflict.

reasonstring · body

Why the plan was refused. Recorded verbatim.

curl
curl -X POST https://api.fibric.io/v1/plans/pl_7c1a/veto \
  -H "Authorization: Bearer $FIBRIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "customer already contacted; hold would double-message" }'
200 OK Response
json
{
  "id": "pl_7c1a",
  "object": "execution_plan",
  "status": "vetoed",
  "vetoed_by": "ops@acme.com",
  "veto_reason": "customer already contacted; hold would double-message",
  "disposed_at": "2026-07-02T15:06:12Z"
}

The action object

Once a plan is disposed, each of its actions becomes an addressable record of what the executor did with it. The disposition is the kernel's ActionDisposition: a trust decision, or DEDUP when the idempotency key had already been applied.

idstring

Unique identifier, prefixed act_. Stable from proposal through disposition.

objectstring

Always action.

plan_idstring

The plan this action belonged to.

connector / tool / args / value / entity_key / idempotency_keymixed

The PlannedAction fields, unchanged from the proposal.

dispositionstring

ALLOW, ALERT, BLOCK, or DEDUP.

okboolean

Whether the disposition completed without error. A BLOCK is ok: false with error: "blocked by trust policy"; a connector failure is ok: false with the upstream error.

errorstring or null

Error detail when ok is false.

receipt_idstring

The immutable receipt written for this action, including refusals.

undoableboolean

true when the tool declares a compensating inverse and the action applied successfully.

undoneboolean

true after a successful undo.

disposed_atstring

RFC 3339 timestamp of the disposition.

GET

List actions

GET/v1/actionsscope actions:read

Returns disposed actions for the tenant, newest first, cursor-paginated.

plan_idstring · query

Only actions from this plan.

entitystring · query

Only actions with this entity_key.

dispositionstring · query

Filter by ALLOW, ALERT, BLOCK, or DEDUP.

sincestring · query

Only actions disposed at or after this RFC 3339 timestamp.

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/actions?entity=order:SO-10884&disposition=ALLOW" \
  -H "Authorization: Bearer $FIBRIC_KEY"
200 OK Response
json
{
  "object": "list",
  "data": [
    {
      "id": "act_91d0",
      "object": "action",
      "plan_id": "pl_7c1a",
      "connector": "cn_7d2f4a",
      "tool": "order.hold",
      "entity_key": "order:SO-10884",
      "idempotency_key": "order-risk:SO-10884:hold",
      "disposition": "ALLOW",
      "ok": true,
      "receipt_id": "rc_5b21",
      "undoable": true,
      "undone": false,
      "disposed_at": "2026-07-02T15:04:41Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
POST

Undo an action

POST/v1/actions/{action_id}/undoscope actions:write

Runs the tool's declared compensating inverse, for example releasing a hold that order.hold placed. The undo is itself an action: it takes the same single-flight lock on the entity_key, carries its own idempotency key derived from the original, and writes its own receipt. The original action's record is never altered; undone flips to true and both receipts stand.

action_idrequiredstring · path

The action to undo. Must be undoable: true and not already undone.

reasonstring · body

Why the action is being reversed. Recorded on the undo receipt.

curl
curl -X POST https://api.fibric.io/v1/actions/act_91d0/undo \
  -H "Authorization: Bearer $FIBRIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "shipment confirmed; releasing the hold" }'
200 OK Response
json
{
  "id": "act_91d0",
  "object": "action",
  "undone": true,
  "undo_action_id": "act_9a44",
  "undo_receipt_id": "rc_5c07",
  "undone_at": "2026-07-02T17:40:02Z"
}

Error cases:

StatusCodeWhen
404not_foundNo action with this id exists for the tenant.
409action_not_undoableThe tool declares no inverse, the action did not apply, or it was already undone.
409entity_lockedThe entity's single-flight lock is held. Retry after Retry-After.

Single-flight and idempotency semantics

Two kernel primitives shape every write on this page. Both are visible in packages/kernel/src/executor.ts and explained in Single-flight & idempotency.

!
A blocked action stays blocked

Approval never overrides a BLOCK. The trust policy fails closed: a side-effecting action with no matching guardrail is refused, and the refusal is receipted with the rule, or the absence of one, that produced it. To let an action through, change the operator's guardrails in the Operators API and let the operator re-propose.