# Payment Intents

A **PaymentIntent** is an agent-proposed financial action that lives as a row in the Ledger. It is the only path to financial execution in Brain. There is no shortcut.

| Property             | Value                                                                |
| -------------------- | -------------------------------------------------------------------- |
| **Layer**            | Ledger row, lifecycle owned by Agent layer                           |
| **Created by**       | Internal or external agents                                          |
| **Executes through** | Provider rails (ACH, NetSuite SuiteTalk, BrainSmartAccount on-chain) |
| **Gates**            | Policy decision plus the §6 13-step pre-execution gate               |

{% hint style="info" %}
PaymentIntents are the **second of two controlled write paths** into the Ledger. The first is Raw extraction. PaymentIntents are the only Ledger write that doesn't originate from a Raw artifact, by design.
{% endhint %}

### Why PaymentIntents are a Ledger entity

A proposed payment is itself a financial fact. Treating it as a row in the Ledger has three consequences:

| Property                         | Effect                                                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Queryable like any other row** | The Wiki, Policy, and other agents can read PaymentIntents the same way they read transactions or obligations |
| **Provenance carries through**   | Every state transition becomes an audit event linked to the row                                               |
| **Policy reads it directly**     | Policy evaluators read PaymentIntent fields and the live Ledger together; no shadow data model                |

### The `ledger_payment_intents` row

```sql
ledger_payment_intents (
  id,
  owner_id,
  created_by_agent_id,
  action_type,            -- ach_outbound | ach_inbound | wire | onchain_transfer | erp_writeback | card_payment | other
  source_account_id,
  destination_counterparty_id,
  amount,
  currency,
  obligation_id,          -- optional
  invoice_id,             -- optional
  status,                 -- proposed | pending_approval | approved | rejected | executed | failed | cancelled
  policy_decision_id,
  approval_ids[],
  execution_receipt_ids[],
  evidence_ids[],
  created_at,
  updated_at
)
```

### Lifecycle

```
proposed
  │
  │ Policy evaluates against live Ledger state
  │
  ├──► auto-allow ─────► approved
  │
  ├──► confirm ────────► pending_approval
  │                       │
  │                       │ approver(s) sign EIP-712
  │                       │
  │                       └──► approved
  │
  └──► reject ─────────► rejected

approved
  │
  │ §6 13-step pre-execution gate
  │
  ├──► gate passes ────► dispatched to rail
  │                       │
  │                       ├──► success ────► executed
  │                       │
  │                       └──► rail failure ► failed
  │
  └──► gate fails ─────► failed
```

[**→ The Pre-Execution Gate**](/protocol/the-pre-execution-gate.md)

### State transitions

| From               | To                 | Trigger                                       |
| ------------------ | ------------------ | --------------------------------------------- |
| `proposed`         | `pending_approval` | Policy returned `confirm`; approvers required |
| `proposed`         | `approved`         | Policy returned `auto`; no human in the loop  |
| `proposed`         | `rejected`         | Policy returned `reject`                      |
| `pending_approval` | `approved`         | All required approvers signed                 |
| `pending_approval` | `rejected`         | Approver explicitly rejected                  |
| `pending_approval` | `cancelled`        | Tenant cancelled before approval              |
| `approved`         | `executed`         | §6 gate passed and rail dispatch succeeded    |
| `approved`         | `failed`           | §6 gate failed or rail dispatch errored       |

Every transition emits an audit event. The full history of any PaymentIntent is reconstructable from `audit_events` ordered by `created_at`.

### How agents create them

Internal agents call `PaymentIntentService.create()`. External agents call the MCP `payment_intent.propose` tool. **Both paths go through the same service method**, so policy evaluation, validation, and audit emission are identical.

```typescript
// Internal agent (TypeScript)
const intent = await paymentIntentService.create({
  ownerId:                "acme",
  createdByAgentId:       "ag_payment_v1",
  actionType:             "ach_outbound",
  sourceAccountId:        "acct_ops",
  destinationCounterpartyId: "cp_aws",
  amount:                 "61404.12",
  currency:               "USD",
  obligationId:           "ob_aws_2025_09",
  idempotencyKey:         "pi_2025_09_aws_001",
});

console.log(intent.status);             // "proposed" → resolved by Policy
console.log(intent.policyDecisionId);   // the PolicyDecision row to inspect
```

### API surface

PaymentIntents are a Ledger entity but their lifecycle endpoints live in the Agent group.

| Method | Endpoint                           | Purpose                                          |
| ------ | ---------------------------------- | ------------------------------------------------ |
| `POST` | `/v1/payment-intents`              | Agent proposes; returns `proposed` PaymentIntent |
| `GET`  | `/v1/payment-intents/{id}`         | Detail with PolicyDecision and audit trail       |
| `POST` | `/v1/payment-intents/{id}/approve` | Human approval for `confirm` intents             |
| `POST` | `/v1/payment-intents/{id}/reject`  | Reject                                           |
| `POST` | `/v1/payment-intents/{id}/execute` | Execute approved intent through rail             |

The MCP equivalent: `payment_intent.propose` for creation. **There is no `payment_intent.execute` on MCP**. Execution is reserved for internal Brain workers running under tenant policy.

### Reading them like any other Ledger row

Because PaymentIntents are a real Ledger entity, the Wiki and other agents query them the same way they query transactions or obligations.

```http
GET /v1/ledger/payment-intents?status=pending_approval&owner=acme
```

Or in the MCP:

```json
{ "method": "resources/read",
  "params": { "uri": "brain://ledger/payment-intents/pi_a1b2c3" } }
```

A Wiki page about a vendor automatically includes their pending PaymentIntents in the **Recent Activity** section.

### Idempotency

Every PaymentIntent creation requires an `idempotencyKey`. Brain stores it in a per-tenant index; retries with the same key return the existing PaymentIntent. This protects against double-proposal under network errors or agent retries.

### What's next

<table data-view="cards"><thead><tr><th></th><th></th><th data-type="content-ref"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>🚪 Pre-Execution Gate</strong></td><td>The 13-step deterministic gate every payment must pass.</td><td><a href="/pages/GcCCOqv3BXHFtEpuypqD">/pages/GcCCOqv3BXHFtEpuypqD</a></td><td></td></tr><tr><td><strong>🤖 Agents</strong></td><td>The three MVP agents and how they propose actions.</td><td><a href="/pages/t9xBO8sSjFElp8ecB6i5">/pages/t9xBO8sSjFElp8ecB6i5</a></td><td></td></tr><tr><td><strong>📋 Policy and Permissioning</strong></td><td>How Policy evaluates PaymentIntents.</td><td><a href="/pages/GSe2ntE9CLqxoQAiQuzG">/pages/GSe2ntE9CLqxoQAiQuzG</a></td><td></td></tr></tbody></table>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.brain.fi/protocol/payment-intents.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
