For the complete documentation index, see llms.txt. This page is also available as Markdown.

Policy API

Compose a policy, sign it, activate it, evaluate proposed actions against it, and lint or simulate before signing. Every Policy route is tenant-scoped: the {tenant_id} (UUID) appears in the path, and your token's tenant must match.

Operation
Endpoint

Get the active policy

GET /v1/policy/{tenant_id}

Compose a candidate policy

POST /v1/policy/{tenant_id}/compose

Sign + activate

POST /v1/policy/{tenant_id}/sign

List versions

GET /v1/policy/{tenant_id}/versions

Evaluate an action

POST /v1/policy/{tenant_id}/evaluate

Lint a draft

POST /v1/policy/{tenant_id}/lint

Simulate against a version

POST /v1/policy/{tenant_id}/simulate

Replay a period

POST /v1/policy/{tenant_id}/simulate-historical

Diff two versions

POST /v1/policy/{tenant_id}/diff

List headless limits

GET /v1/policy/headless-limits

Upsert a headless limit

PUT /v1/policy/headless-limits

Delete a headless limit

DELETE /v1/policy/headless-limits

There is no separate register or revoke endpoint. Activation happens at sign, and superseding a version means signing a new one. The previous active version is recorded in versions history.

Headless Limits

Headless limits are server-managed policy rules for decision-only external authorizations. They let a tenant admin set a per-agent, per-action, per-currency approval envelope without EIP-712 signing.

They are the policy surface behind the headless launch path:

  1. An owner creates a tenant agent and receives one brain_ak_... key.

  2. The agent exchanges that key for a five-minute JWT scoped to payment_intent:propose.

  3. The agent calls POST /v1/authorizations with execution: "external".

  4. The active policy plus any headless limit rules returns approved, needs_review, or blocked.

The route set is:

GET requires policy:read. PUT and DELETE require policy:write and an active admin member, either from a user session or a tenant service credential that asserts that member. Agent principals are refused.

PUT request:

Allowed action types are ach_outbound, wire, card, and erp_writeback. Settlement and custody actions are refused.

The API merges one generated rule per agent, action, and currency into the active policy. It never replaces unrelated tenant rules. Generated rules carry external_authorization_only: true and managed_by: headless_limits_api, are unsigned by design, and only apply to POST /v1/authorizations with execution: external. Internal execution keeps the normal fiat human approval floor.

Every mutation activates a new policy version and emits an audit event with the before and after values. The linter still blocks unsafe generated rules. Auto approval still requires a registered counterparty that is not paused, is not an agent counterparty, and has accepted provenance or verification.

Compose a Candidate Policy

The DSL is structured JSON, not prose. The compose route validates it and returns the canonical hash plus the EIP-712 typed-data payload the tenant signers will sign.

The content wrapper and the numeric version are mandatory. An optional top-level quorum_required sets how many distinct authorized signers sign needs (default 1).

Response:

execute is one of auto | confirm | reject. These are the rule-level outcomes that produce the policy decision (allow | confirm | reject).

Sign and Activate

Each required signer signs the signing_payload from compose, then someone (any caller with policy:sign) submits the policy_id and all signatures together:

200 OK with the serialized policy, an activated flag, and any activation warnings:

The signature count reaching quorum_required flips the policy to active (activated: true). Below quorum, the signatures are recorded and the policy stays pending_signatures. A signature that fails to verify, a duplicate signer, or a signer that is not an authorized tenant signer returns 401 with policy_signature_invalid. Signing a policy that is not awaiting signatures returns 409 with policy_quorum_not_met.

Get the Active Policy

Returns the currently active Policy for the tenant (404 if none has been activated). For a specific historical version, use /versions.

List Versions

Evaluate an Action

Evaluate an action against the active policy. This endpoint does not propose or reserve a payment, but every call emits a policy.evaluate audit event.

The endpoint uses the same policy VM as the section 6 gate, but it is not a complete gate simulation. Its request parser accepts only kind, counterparty_id, amount, agent_role, and timestamp. It does not accept or populate agent_id, tenant_category, confidence, evidence_score, risk_level, spend_in_window, tx_count_in_window, behavior_hash, or action_id. Rules using those inputs can therefore evaluate differently here than they do with a fully populated gate action.

Three Possible Decisions

Decision
Meaning

allow

Rule matched with execute: "auto"; action can proceed straight to the §6 gate

confirm

Rule matched with execute: "confirm"; named required_approvers must sign first

reject

Rule matched with execute: "reject", or no rule matched the default-deny vocabulary

Casing is lowercase. allow | confirm | reject. The historical-simulation counters mirror it (would_allow, would_confirm, would_reject).

Unmatched high-risk agent actions

The policy VM remains deny-by-default. A normal unmatched rule evaluation still returns reject, and low-stakes or money-path proposal types keep their existing fail-closed behavior.

There is one compatibility wrapper for reviewable high-risk non-money proposals: PolicyService.evaluateLegacy converts unmatched agent_action fallthroughs for collections, fraud_anomaly, and vendor_risk to confirm with required_approvers: ["signer"]. The same fallback also applies to stored high-risk action types such as flag_transaction, block_payment, flag_vendor_risk, freeze_card, create_dispute_draft, require_approval, and escalate. This keeps suspicious transactions, vendor-risk holds, and collections follow-ups visible for human review instead of silently closing them as policy rejections. payment proposals are deliberately not included in this fallback.

Decision vocabulary across surfaces

allow | confirm | reject is the canonical protocol decision. The rule-level execute field and the SDK use aliases that map 1:1; the PaymentIntent status reflects the same outcome:

Protocol decision (HTTP/MCP)

Rule-level execute

SDK decision.outcome / action.status

Resulting PaymentIntent status

allow

auto

auto

approved or pending_approval*

confirm

confirm

needs_approval

pending_approval

reject

reject

rejected

rejected

Compare against allow | confirm | reject over HTTP/MCP; the auto | needs_approval | rejected triple is an SDK alias, not the protocol vocabulary.

* An allow decision still enters pending_approval when the hard human-approval floor applies. The floor always applies to onchain_transfer and escrow_release, applies to wire while the fiat floor is enabled, and applies to ach_outbound, ach_inbound, card_payment, and x402_settle unless the matched rule carries a covering signed autonomous amount cap.

Action Vocabulary

The evaluate action.kind is one of the following. There is no rail field on the evaluate action.

kind

Domain

outbound_payment

Money leaving (ACH, wire, on-chain, x402, escrow)

inbound_payment

Money arriving

ledger_write

A Ledger-row mutation (e.g. agent normalization)

onchain_tx

A non-payment on-chain transaction

agent_action

A non-money agent action gated by policy

any

Only valid inside a rule's applies_to catch-all

A rule's applies_to accepts the same kind values, including any. (The PaymentIntent layer uses a separate, broader action_type set. ach_outbound, wire, x402_settle, etc.. Those map onto the kind values internally.)

Proposal read APIs expose a separate public proposal type. Stored agent action_type values are preserved as stored_action_type and then mapped to a public proposal type through the proposal read model. For example, flag_transaction maps to fraud_anomaly, block_payment maps to vendor_risk, and propose_match maps to reconciliation. Ambiguous stored actions such as notify, escalate, create_task, and recommend_action use the agent role or kind instead of action-name guessing.

Lint a Draft

Before composing, run a linter against a policy-content blob to catch shape / semantic problems:

Simulate Against a Version

Replay a single action against a specific historical policy version:

Returns { "decision": <same shape as /evaluate>, "policy_version": 3 }. Unlike /evaluate, simulate wraps the decision with the policy_version it replayed against.

Replay a Period

Replay every action in a time window against a candidate (unsigned) policy. Useful for asking "would version 5 have changed anything?":

Diff Two Versions

What's Next

📋 Policy and Permissioning

The conceptual model.

📜 BrainPolicyRegistry

The on-chain registry.

Last updated