action
1 to 100 characters of letters, digits and . _ - :. Approval policies can match it.
Documentation
Agents ask. People decide.An agent about to do something with consequences asks Blue Falnoa first and carries on only when people have approved.
Message usThe 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 expired1 to 100 characters of letters, digits and . _ - :. Approval policies can match it.
What a person reads when deciding, up to 500 characters.
A JSON object up to 16 KiB. People see it in full; the audit log keeps only its field names.
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 PermissionErrorTypeScript
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.
All guides
Create a key, send a message and see the agent connected.
Conversations, knowledge search, the doctor check and A2A steps.
Clients for Python and TypeScript, adapters for OpenAI and LangChain, and n8n.
Verify an evidence bundle offline with bf-verify, or in your browser.