---
title: Ledger Agent
description: Keep double-entry books for everything your agent does. Post journal entries on payment events, read balances, and reconcile against signed agentic receipts.
---

<MetaStrip items={[
  { label: "TIME", value: "~15 min" },
  { label: "STACK", value: (<><ServerChip name="Node.js" accent /><span style={{ color: "var(--color-fd-muted-foreground)", fontSize: 12 }}>no LLM required</span></>) },
  { label: "RAIL", value: (<><ServerChip name="midaz" accent /><span style={{ color: "var(--color-fd-muted-foreground)", fontSize: 12 }}>shared rail</span></>) },
]} />

Agents that move money need books an auditor accepts. A payment log is not accounting: it tells you a PSP call happened, not where the money sits now or whether the numbers still add up. This cookbook builds a ledger agent that posts a double-entry journal entry for every commerce event, reads balances on demand, creates accounts as new users appear, and pulls the signed agentic receipts (mandate, payment, delivery) to reconcile the books against what actually settled.

`codespar_ledger` is a **shared rail**: buy-side and sell-side agents use the same books. It records what happened; [`codespar_pay`](/docs/concepts/meta-tools/pay) and [`codespar_charge`](/docs/concepts/meta-tools/charge) are what move the money.

<PixFlow
  steps={[
    { label: "Post entries", tool: "codespar_ledger", description: "action: entry (default). Debits must equal credits, amounts in minor units", iconKey: "doc" },
    { label: "Read balances", tool: "codespar_ledger", description: "action: balance. Per-asset balances for any account", iconKey: "search" },
    { label: "Pull receipts", tool: "codespar_ledger", description: "action: receipts. Signed agentic receipts for a consumer or mandate", iconKey: "send" },
    { label: "Reconcile", tool: "your code", description: "Match each receipt's payment to a journal entry; flag anything unmatched", iconKey: "chat" },
  ]}
/>

## Prerequisites

```bash
npm install @codespar/sdk
```

- A `csk_test_` API key from the [dashboard](https://codespar.dev/dashboard).
- A **ledger connection**. The ledger routes to the tenant's own self-hosted [Lerian Midaz](https://github.com/LerianStudio/midaz) instance: the operator connects it once in `/dashboard/auth-configs` with the tenant's `base_url` and credentials (stored in the vault). See [codespar_ledger](/docs/concepts/meta-tools/ledger) for the connection setup.

<Callout>
Ledger writes are bookkeeping, not settlement. Posting an entry never moves real money, so you can run this whole cookbook in test mode without a PSP connection.
</Callout>

## Step 1: Create the accounts

The chart of accounts is yours. A minimal setup for a marketplace agent: one wallet account per user, one revenue account, and one external account representing the PSP side.

```typescript title="ledger-agent.ts"
import { CodeSpar } from "@codespar/sdk";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY! });

const session = await codespar.create("ledger-agent", {
  servers: ["midaz"],
  metadata: { source: "worker:books" },
});

await session.ledger({
  action: "account",
  alias: "@wallet/user_123",
  name: "User 123 wallet",
  type: "deposit",
});

await session.ledger({
  action: "account",
  alias: "@revenue/store",
  name: "Store revenue",
  type: "deposit",
});

await session.ledger({
  action: "account",
  alias: "@external/psp",
  name: "PSP clearing",
  type: "external",
});
```

Account creation is idempotent on the alias in practice: re-running setup against an existing alias fails cleanly rather than duplicating, so keep it in your worker's boot path.

## Step 2: Post an entry for every payment event

Wire your payment webhook (or your agent's post-payment hook) to post a journal entry. Debits (`source`) must equal credits (`destination`), amounts are **minor units** (centavos, cents), and the ledger is asset-agnostic, so BRL, USD, and USDC live in the same books with a per-asset `scale`.

```typescript
async function onPaymentSettled(event: {
  payment_id: string;
  tool_call_id: string;
  amount: number; // centavos
  consumer_id: string;
}) {
  await session.ledger({
    action: "entry",
    asset: "BRL",
    scale: 2,
    source: [{ account: `@wallet/${event.consumer_id}`, amount: event.amount }],
    destination: [{ account: "@revenue/store", amount: event.amount }],
    description: `Payment ${event.payment_id} settled`,
    metadata: {
      payment_id: event.payment_id,
      tool_call_id: event.tool_call_id,
    },
  });
}
```

The `metadata` block is the join key for Step 4: stamp every entry with the `payment_id` and `tool_call_id` that caused it, and reconciliation becomes a lookup instead of an investigation.

Entries can be n:n. A marketplace sale that splits into seller payout plus platform fee is one balanced entry with one `source` and two `destination` legs, not two entries that can drift apart.

## Step 3: Read balances

```typescript
const balance = await session.ledger({
  action: "balance",
  account: "<account-uuid>",
});
// balance lists per-asset available amounts for the account
```

Use this for pre-flight checks (is there enough in the wallet before the agent commits to a purchase?) and for the end-of-day snapshot your reconciliation report starts from.

## Step 4: Pull receipts and reconcile

What a receipt looks like when it comes back:

<ReceiptCard />

Every governed payment produces a signed **agentic receipt** (the Control Record): the chain from mandate to payment to delivery for that transaction. `action: "receipts"` lists them for a consumer or mandate; `action: "receipt"` fetches one.

```typescript
const receipts = await session.ledger({
  action: "receipts",
  consumer_id: "con_abc123",
  mandate_id: "cm_gW...", // optional: narrow to one mandate
});

for (const r of receipts.items) {
  const entry = await books.findEntryByPaymentId(r.payment_id); // your Step 2 metadata
  if (!entry) {
    await flagForReview({
      reason: "receipt_without_entry",
      receipt_id: r.receipt_id,
      payment_id: r.payment_id,
    });
  }
}
```

Run the match in both directions: a receipt without an entry means the books missed a settlement; an entry without a receipt means the books claim money moved that the platform never attested. Both are findings an auditor cares about, and both come out of one loop over `receipts.items`.

## MCP variant: books in natural language

The same rail is exposed through the CodeSpar MCP server, so an operator can work the books from Claude, Codex, or any MCP host without writing the worker above:

```text
"Post a journal entry: R$150,00 from @wallet/user_123 to @revenue/store,
description 'Order #1234 settled'. Then show me the revenue balance and
pull the receipts for mandate cm_gW... so we can check they match."
```

The agent calls `codespar_ledger` three times (`entry`, `balance`, `receipts`) and reports the mismatches. Governance is identical: every call lands on the [audit chain](/docs/concepts/audit-chain) either way.

## What a receipt proves today

Be precise about what you are reconciling against. A receipt today reliably binds the **mandate** (what the agent was authorized to spend, signed) and the **payment** (what was executed, with rail-level identifiers). The **delivery** leg is evolving: binding an external proof of delivery, such as a fiscal document or carrier confirmation, into the signed record is not complete for every rail. Treat the receipt as proof of authorized payment, and keep delivery evidence in your own records until the binding lands. The receipt read itself is an audited event, so pulling receipts leaves its own trace on the chain.

## Next steps

<NextStepsGrid items={[
  { label: "CONCEPT", title: "codespar_ledger", description: "Full args shape: entry, balance, account, and the Midaz connection setup.", href: "/docs/concepts/meta-tools/ledger" },
  { label: "CONCEPT", title: "Audit chain", description: "The hash-chained record every ledger call and payment lands on.", href: "/docs/concepts/audit-chain" },
  { label: "CONCEPT", title: "Wallets", description: "Programmable spend with balances backed by the ledger.", href: "/docs/concepts/wallets" },
  { label: "COOKBOOK", title: "Agent with a Wallet", description: "The buy-side counterpart: an agent that spends from a governed wallet.", href: "/docs/cookbooks/agent-with-wallet" },
]} />
