Skip to content

Documentation

Ask first.Then act.

Agents ask. People decide.

Message us

The request

Send the action with the ingest key. The same idempotency_key from the same agent returns the same request (200 instead of 201), so a retry never creates a second one. Poll the request until it is decided.

POST /api/agent/v1/actions
Authorization: Bearer bfa_…

{
  "action": "payments.transfer",
  "summary": "Pay supplier invoice 42",
  "params": { "amount": 40000, "currency": "EUR" },
  "idempotency_key": "invoice-42",
  "expires_in_secs": 3600
}

→ 201 { "id": "…", "status": "pending", "required_approval": "user", "expires_at": "…" }
GET /api/agent/v1/actions/{id}   # until status is approved, rejected or expired

action

1 to 100 characters of letters, digits and . _ - :. Approval policies can match it.

summary

What a person reads when deciding, up to 500 characters.

params

A JSON object up to 16 KiB. People see it in full; the audit log keeps only its field names.

expires_in_secs

60 to 604800, default 86400. A request nobody decided in time becomes expired.

Who decides, and how strict

How strict the check is is never the agent’s choice. Every request needs at least one approval from an administrator or operator. The approval policies that guard workflow steps can raise that to an administrator, or to two different people with one administrator, by amount, risk class or time of day. It is fixed when the request is made.

A refusal, like an approval, needs a written reason of at least 5 characters. Every step is audited, and the agent sees only its own requests and never other people’s notes.

People decide in the app, in Action gateway, or with a write service token: GET /api/gateway/actions?status=pending, then POST /api/gateway/actions/{id}/approve or /reject with a note.

With the SDKs

Python wraps it as a decorator, TypeScript as withApproval.

from blue_falnoa import AgentClient

bf = AgentClient(url, "bfa_…")

@bf.requires_approval("payments.transfer", "Pay supplier invoice")
def pay(invoice, amount):
    ...  # runs only after people approve

pay(invoice="42", amount=40000)   # a refusal or expiry raises PermissionError

TypeScript

import { AgentClient } from "@blue-falnoa/sdk";

const bf = new AgentClient(url, "bfa_…");
await bf.withApproval(
  "payments.transfer",
  "Pay supplier invoice 42",
  { amount: 40000, currency: "EUR" },
  () => payInvoice("42"), // runs only after people approve
);

What it does not do

The gateway is a control point only for agents that call it. Nothing stops an agent from skipping the call. Requests from agents that are not approved in the registry, including after an emergency stop, are refused with 403.