# AgentLedger > AgentLedger is the financial control layer between AI agents and payment methods. Set per-agent budgets, require human or AI approvals, keep a full audit trail, and release card details only when the rules are met. Open-source (AGPL-3.0), single Go binary (~11 MB), zero runtime dependencies. Written in under 2,500 lines of Go with SQLite storage and AES-256-GCM encryption. ## Installation Download the latest binary for your platform from GitHub Releases: https://github.com/agentledgerhq/agentledger/releases ### macOS & Linux ```bash # Rename to remove the platform suffix and make executable mv agentledger-linux-amd64 agentledger # adjust filename for your platform chmod +x agentledger # Move to a directory on your PATH (optional but recommended) sudo mv agentledger /usr/local/bin/ # Verify agentledger ``` On macOS, if you see a Gatekeeper warning on first run, go to System Settings → Privacy & Security and click Allow Anyway. ### Windows ```powershell # Rename the downloaded file Rename-Item agentledger-windows-amd64.exe agentledger.exe # Run from the current directory .\agentledger.exe # Or add the directory to your PATH for system-wide access. ``` Available platform binaries: `linux-amd64`, `linux-arm64`, `darwin-amd64`, `darwin-arm64`, `windows-amd64`, `windows-arm64`. ## MCP Servers Setup The MCP servers are a Node.js package that wraps the REST API for use with MCP-compatible clients — Claude Desktop, Cursor, Windsurf, OpenClaw, and others. Requires Node.js >= 18 and a running AgentLedger instance. ### Install from the repository ```bash git clone https://github.com/agentledgerhq/agentledger cd agentledger/apps/mcp npm install npm run build ``` A standalone `npx agentledger-mcp` installer (no clone required) is coming soon. ### Configure your MCP client Add both servers to your MCP client configuration. For Claude Desktop, edit `claude_desktop_config.json`: ```json { "mcpServers": { "agentledger-user": { "command": "node", "args": ["/path/to/agentledger/apps/mcp/dist/index.js", "user"], "env": { "AGENTLEDGER_USER_TOKEN": "alu_your-token-here" } }, "agentledger-supervisor": { "command": "node", "args": ["/path/to/agentledger/apps/mcp/dist/index.js", "supervisor"], "env": { "AGENTLEDGER_SUPERVISOR_TOKEN": "als_your-token-here", "AGENTLEDGER_SUPERVISOR_TIER": "1" } } } } ``` Replace `/path/to/agentledger` with the actual path where you cloned the repository. Replace the token values with the tokens from your `.env` file (generated during `agentledger init`). The same JSON structure works for Cursor, Windsurf, and other MCP-compatible tools — consult their documentation for the config file location. ### Supervisor tiers in MCP Set `AGENTLEDGER_SUPERVISOR_TIER` to control which tools the supervisor server exposes. Tier 1 (default) covers approve/deny. Tier 2 adds budget management. Tier 3 adds vault replacement. This must match the tier set during `agentledger init --supervisor-tier N`. ## Quick Start ```bash # 1. Initialize (generates tokens, encryption key, database) agentledger init # 2. Vault a payment card agentledger vault add # 3. Set a budget for an agent (e.g. $50.00 cumulative) agentledger budget set agent-1 5000 # 4. Start the server agentledger serve # Listening on http://127.0.0.1:9119 ``` The server is now ready. Agents authenticate with the tokens printed during `init`, which are stored in `.env`. ## Authentication AgentLedger uses two bearer tokens with distinct roles. Tokens are generated during `agentledger init` and use prefixes to prevent accidental swap. | Role | Env Var | Prefix | Purpose | |------|---------|--------|---------| | User agent | `AGENTLEDGER_USER_TOKEN` | `alu_` | Initiate spends, check budget, poll status, read history | | Supervisor agent | `AGENTLEDGER_SUPERVISOR_TOKEN` | `als_` | Approve/deny, manage budgets, credit, vault update (tiered) | Pass the token in the `Authorization` header: ``` Authorization: Bearer alu_your-user-token-here ``` ### Supervisor Tiers Supervisor capabilities are gated by tier, set at `agentledger init --supervisor-tier N`: | Tier | Default? | Capabilities | |------|----------|-------------| | 1 | Yes | Approve, deny, list pending | | 2 | Opt-in | + Set budget, credit budget | | 3 | Opt-in, explicit consent | + Update vault (replace card) | ### Multiple Agents There is no agent registration step. An agent comes into existence the moment you set a budget for it — the `agent_id` is simply a free-form string you choose. You can run as many logical agents as you like, each with its own independently tracked budget and transaction history: ```bash agentledger budget set researcher-agent 5000 agentledger budget set purchasing-agent 20000 agentledger budget set ops-agent 10000 ``` Agents authenticate using the shared user token (`alu_`) — there is no per-agent credential. The `agent_id` field in `POST /authorize` is self-reported by the calling agent and determines which budget is checked and consumed. The security boundary is the token, not the agent ID: any bearer of the user token can submit requests under any agent ID string. All agents share a single vaulted card. Budget accounting and transaction history are tracked per `agent_id`, but the underlying payment credential is the same for all of them. ### Running multiple isolated instances For full isolation between separate groups of agents — for example, two independent "virtual companies" — you can run two separate AgentLedger instances simultaneously, each in its own directory with its own `.env`, encryption key, token pair, card vault, and database. Use `AGENTLEDGER_PORT` to assign each instance a different port: ```bash # Company A — default port cd ~/company-a agentledger init agentledger serve # Listening on http://127.0.0.1:9119 # Company B — different port (separate terminal) cd ~/company-b agentledger init # Edit .env: uncomment and set AGENTLEDGER_PORT=9120 agentledger serve # Listening on http://127.0.0.1:9120 ``` Point each set of agents at the appropriate base URL (`AGENTLEDGER_BASE_URL` in the MCP server config). Each instance is completely isolated — separate tokens, separate encryption key, separate card, separate budget and transaction ledger. ### Running on a VPS AgentLedger works on a VPS or cloud server, but **requires a reverse proxy with TLS**. The API transmits bearer tokens and card credentials — these must never travel over plain HTTP on a network. **Recommended setup:** keep AgentLedger bound to `127.0.0.1` (the default) and place Caddy or nginx in front for automatic HTTPS. ```bash # 1. Initialize and start AgentLedger (binds to 127.0.0.1:9119 by default) agentledger init agentledger vault add agentledger serve # 2. Install Caddy (Ubuntu/Debian example) sudo apt install -y caddy # 3. Create /etc/caddy/Caddyfile # Replace agentledger.example.com with your domain agentledger.example.com { reverse_proxy 127.0.0.1:9119 } # 4. Restart Caddy (auto-obtains a Let's Encrypt certificate) sudo systemctl restart caddy ``` Your agents now connect to `https://agentledger.example.com` instead of `http://127.0.0.1:9119`. Set `AGENTLEDGER_BASE_URL=https://agentledger.example.com` in your MCP client config. **Firewall:** Only expose port 443 (HTTPS) to the internet. Port 9119 should remain accessible only from localhost. ```bash # UFW (Ubuntu) sudo ufw allow 443/tcp sudo ufw deny 9119/tcp ``` **Advanced — binding to all interfaces:** If you need AgentLedger to accept connections directly (e.g. inside a private network or Docker bridge), set `AGENTLEDGER_HOST` in your `.env`: ```bash # .env AGENTLEDGER_HOST=0.0.0.0 ``` The default is `127.0.0.1` (localhost only). **Do not bind to `0.0.0.0` without TLS termination in front** — tokens and card data would be transmitted in plaintext. ## Ledger Semantics AgentLedger uses an **authorize-is-final** model. Once the budget is deducted and the card is released, the spend is permanent. There is no agent-accessible path to restore budget after card release. ### Transaction Status Lifecycle ``` POST /authorize → spent (auto mode: budget consumed, card released immediately) → pending (manual mode: awaiting approval) → spent (approved: card released) → denied (denied or TTL expired: budget refunded) → denied (insufficient budget or invalid request) ``` - **Budget deduction is atomic.** Checked and reserved in a single operation. - **Pending transactions expire.** Controlled by `AGENTLEDGER_APPROVAL_TTL` (default: `30m`). Expired transactions are auto-denied with full budget refund. - **Webhook notifications.** Set `TRANSACTION_CALLBACK_URL` in `.env` to receive an HTTP POST on every transaction outcome (`spent`, `pending`, `denied`). See Webhook section below. - **Budget restoration** requires a supervisor (Tier 2+) issuing `POST /credit`. ## REST API Reference ### GET /health Health check. Returns `200` if the server is running. No auth required. ```bash curl http://127.0.0.1:9119/health ``` Response: ```json { "status": "ok" } ``` ### POST /authorize Request payment authorization. Auth: User token (`alu_`). If the agent's budget is sufficient, funds are reserved and card details may be returned immediately (auto mode) or held for approval (manual mode). **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `amount` | integer | Yes | Amount in cents (e.g. `2500` = $25.00). Must be > 0. | | `merchant` | string | Yes | Merchant name | | `purpose` | string | Yes | Why this payment is needed | | `requesting_agent_id` | string | Yes | Agent identifier | | `metadata` | object | No | Arbitrary key-value pairs | ```bash curl -X POST http://127.0.0.1:9119/authorize \ -H "Authorization: Bearer alu_..." \ -H "Content-Type: application/json" \ -d '{ "amount": 2500, "merchant": "AWS", "purpose": "EC2 instance for data pipeline", "requesting_agent_id": "agent-1" }' ``` **Response (auto mode — spent):** ```json { "status": "spent", "transaction_id": "tx_abc123", "holder_name": "Jane Doe", "card_number": "4111111111111111", "expiry_date": "12/27", "cvv": "123", "country": "US", "zip_code": "94105" } ``` **Response (manual mode — pending):** ```json { "status": "pending", "transaction_id": "tx_abc123", "expires_at": "2026-04-12T20:30:00Z" } ``` **Response (denied):** ```json { "status": "denied", "reason": "insufficient budget" } ``` ### GET /status/{tx_id} Poll the current status of a transaction. Auth: User token (`alu_`). In manual mode, this is how the user agent retrieves card details after a supervisor approves the transaction. Card details are included only when status is `spent` and the `agent_id` query parameter matches the transaction's owning agent. **Query Parameters:** | Parameter | Required | Description | |-----------|----------|-------------| | `agent_id` | Yes (for card data) | Must match the `requesting_agent_id` used in the original `POST /authorize`. If omitted or mismatched, the response includes status and metadata but no card fields. | ```bash curl http://127.0.0.1:9119/status/tx_abc123?agent_id=agent-1 \ -H "Authorization: Bearer alu_..." ``` **Response (pending):** ```json { "transaction_id": "tx_abc123", "status": "pending", "amount": 2500, "merchant": "AWS", "purpose": "EC2 instance for data pipeline", "agent_id": "agent-1", "expires_at": "2026-04-12T20:30:00Z" } ``` **Response (spent, with matching agent_id):** ```json { "transaction_id": "tx_abc123", "status": "spent", "amount": 2500, "merchant": "AWS", "purpose": "EC2 instance for data pipeline", "agent_id": "agent-1", "holder_name": "Jane Doe", "card_number": "4111111111111111", "expiry_date": "12/27", "cvv": "123", "country": "US", "zip_code": "94105" } ``` If `agent_id` is omitted or does not match, the response contains the same fields except the card fields are excluded. ### GET /budget/{agent} Check the remaining budget for a specific agent. Auth: User or Supervisor token. ```bash curl http://127.0.0.1:9119/budget/agent-1 \ -H "Authorization: Bearer alu_..." ``` **Response:** ```json { "agent_id": "agent-1", "limit": 5000, "used": 2500, "available": 2500, "period": "cumulative" } ``` ### GET /transactions/{agent} Retrieve the full transaction history for an agent. Auth: User or Supervisor token. ```bash curl http://127.0.0.1:9119/transactions/agent-1 \ -H "Authorization: Bearer alu_..." ``` **Response:** ```json [ { "id": "tx_abc123", "agent_id": "agent-1", "amount": 2500, "merchant": "AWS", "purpose": "EC2 instance for data pipeline", "status": "spent", "created_at": "2026-04-12T14:00:00Z", "updated_at": "2026-04-12T14:00:00Z" } ] ``` ### GET /pending List all transactions currently awaiting approval. Auth: Supervisor token, Tier 1+. ```bash curl http://127.0.0.1:9119/pending \ -H "Authorization: Bearer als_..." ``` **Response:** ```json [ { "id": "tx_abc123", "agent_id": "agent-1", "amount": 2500, "merchant": "AWS", "purpose": "EC2 instance for data pipeline", "expires_at": "2026-04-12T20:30:00Z", "created_at": "2026-04-12T14:00:00Z" } ] ``` ### POST /approve Approve a pending transaction. Auth: Supervisor token, Tier 1+. The transaction transitions to `spent` and the reserved budget is consumed. Card details are not returned in this response — the user agent retrieves them by polling `GET /status/{tx_id}?agent_id=...`. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `transaction_id` | string | Yes | ID of the pending transaction | ```bash curl -X POST http://127.0.0.1:9119/approve \ -H "Authorization: Bearer als_..." \ -H "Content-Type: application/json" \ -d '{"transaction_id": "tx_abc123"}' ``` **Response:** ```json { "transaction_id": "tx_abc123", "status": "spent", "amount": 2500, "merchant": "AWS", "purpose": "EC2 instance for data pipeline", "agent_id": "agent-1" } ``` ### POST /deny Deny a pending transaction. Reserved budget is fully refunded. Auth: Supervisor token, Tier 1+. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `transaction_id` | string | Yes | ID of the pending transaction | ```bash curl -X POST http://127.0.0.1:9119/deny \ -H "Authorization: Bearer als_..." \ -H "Content-Type: application/json" \ -d '{"transaction_id": "tx_abc123"}' ``` **Response:** ```json { "status": "denied" } ``` ### POST /budget/{agent} Create or update the spending budget for an agent. Auth: Supervisor token, Tier 2+. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `limit` | integer | Yes | Budget limit in cents | | `period` | string | No | `cumulative` (default) or `monthly` | ```bash curl -X POST http://127.0.0.1:9119/budget/agent-1 \ -H "Authorization: Bearer als_..." \ -H "Content-Type: application/json" \ -d '{"limit": 10000, "period": "monthly"}' ``` **Response:** ```json { "agent_id": "agent-1", "limit": 10000, "used": 0, "available": 10000, "period": "monthly", "period_start": "2026-04-01T00:00:00Z" } ``` ### POST /credit Restore previously consumed budget. Use after a payment bounces or a refund is received. A reason is required for audit trail. Auth: Supervisor token, Tier 2+. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `agent_id` | string | Yes | Agent to credit | | `amount` | integer | Yes | Amount to restore in cents | | `reason` | string | Yes | Audit reason (e.g. "Payment bounced") | ```bash curl -X POST http://127.0.0.1:9119/credit \ -H "Authorization: Bearer als_..." \ -H "Content-Type: application/json" \ -d '{"agent_id": "agent-1", "amount": 2500, "reason": "Merchant refund received"}' ``` **Response:** ```json { "agent_id": "agent-1", "credited": 2500, "new_used": 0, "new_limit": 5000, "available": 5000 } ``` ### POST /vault/update Replace the vaulted payment card. High-privilege operation — only available when Tier 3 was explicitly enabled during `agentledger init`. All future approved transactions will use the new card. Auth: Supervisor token, Tier 3. **Request Body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `holder_name` | string | Yes | Cardholder name | | `card_number` | string | Yes | Card number (digits only) | | `expiry_date` | string | Yes | Expiry in MM/YY format | | `cvv` | string | Yes | Security code | | `country` | string | Yes | Two-letter country code | | `zip_code` | string | Yes | Billing ZIP / postal code | ```bash curl -X POST http://127.0.0.1:9119/vault/update \ -H "Authorization: Bearer als_..." \ -H "Content-Type: application/json" \ -d '{ "holder_name": "Jane Doe", "card_number": "4111111111111111", "expiry_date": "12/27", "cvv": "123", "country": "US", "zip_code": "94105" }' ``` **Response:** ```json { "status": "vault updated" } ``` ## Webhook (Transaction Notifications) Set `TRANSACTION_CALLBACK_URL` in your `.env` to receive an HTTP POST on every `POST /authorize` outcome. The URL must use `http://` or `https://`. A single URL receives all event types — filter by the `event` field. ```bash # .env TRANSACTION_CALLBACK_URL=http://your-service/webhook ``` The request is fired asynchronously (non-blocking) with a 10-second timeout. A non-2xx response or timeout is logged as a warning but does not affect the transaction. **Scope:** Webhooks fire only on `POST /authorize` outcomes. Subsequent state changes from `POST /approve`, `POST /deny`, or TTL-based auto-expiry do not currently trigger webhooks. To monitor approval outcomes, have your supervisor agent or handler poll `GET /status/{tx_id}` after acting on a `transaction.pending` event. ### HMAC Signature Verification Each webhook request includes an `X-Agentledger-Signature` header containing an HMAC-SHA256 hex digest of the request body, signed with `AGENTLEDGER_WEBHOOK_SECRET` (auto-generated during `agentledger init`). Use this to verify that the webhook came from your AgentLedger instance. ``` # Header format X-Agentledger-Signature: sha256= # Verification (Python example) import hmac, hashlib expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() assert hmac.compare_digest(expected, received_sig) ``` **Recommended for manual approval mode.** When running with `APPROVAL_MODE=manual`, configuring this webhook is the preferred way to get notified that a transaction is waiting for approval — rather than having your supervisor agent poll `GET /status/{tx_id}`. The `transaction.pending` event fires immediately when the transaction is created, and the payload includes `expires_at` so your handler knows the approval deadline. ### Event types | Event | When | |-------|------| | `transaction.spent` | Auto mode: budget consumed and card released immediately | | `transaction.pending` | Manual mode: awaiting supervisor approval. `expires_at` is included | | `transaction.denied` | Insufficient budget or invalid request — no budget consumed | ### Payload ```json { "event": "transaction.pending", "transaction_id": "tx_abc123", "amount": 1500, "merchant": "AWS", "purpose": "Spin up EC2 instance for crawl job", "agent_id": "crawler-agent", "expires_at": "2026-04-12T11:30:00Z", "created_at": "2026-04-12T11:00:00Z", "metadata": { "job_id": "crawl-42" } } ``` | Field | Type | Description | |-------|------|-------------| | `event` | string | See event types above | | `transaction_id` | string | Use with `POST /approve`, `POST /deny`, or `GET /status/{tx_id}` | | `amount` | integer | Amount in cents | | `merchant` | string | Merchant name from the authorize request | | `purpose` | string | Agent-provided reason for the spend | | `agent_id` | string | Requesting agent identifier | | `expires_at` | string (ISO 8601) | Only present on `transaction.pending`; deadline before auto-denial | | `created_at` | string (ISO 8601) | When the transaction was created | | `metadata` | object | Optional key-value pairs passed in the authorize request | ## MCP Tools Overview AgentLedger ships two MCP servers matching the two-token role model. Each server is a thin stdio proxy: it receives MCP tool calls and forwards them as HTTP requests to the AgentLedger REST API. Both are included in the repository under `apps/mcp/` — see MCP Servers Setup above for installation instructions. The `x-base-url` defaults to `http://127.0.0.1:9119` and is configurable via `AGENTLEDGER_BASE_URL`. | Set | Token | Tools | |-----|-------|-------| | User agent | `alu_` | `request_payment`, `check_budget`, `get_status`, `get_transactions` | | Supervisor agent | `als_` | `list_pending_transactions`, `approve_transaction`, `deny_transaction`, `set_budget`, `credit_budget`, `update_vault`, `get_transactions` | Ship the user MCP config to your spending agent and the supervisor MCP config to your overseer agent. Never give both to the same agent unless you explicitly want full delegation. ## User Agent MCP Tools ### request_payment Request payment authorization. Maps to `POST /authorize`. In auto mode, card details are returned immediately. In manual mode, the transaction enters `pending` and the agent must poll `get_status` to retrieve card details after supervisor approval. Authorize is final — there is no confirmation step. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `amount` | integer | Yes | Amount in cents | | `merchant` | string | Yes | Merchant name | | `purpose` | string | Yes | Reason for payment | | `requesting_agent_id` | string | Yes | Agent identifier | ### check_budget Check available budget before spending. Accepts either user or supervisor token. Maps to `GET /budget/{agent}`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | Yes | Agent identifier | ### get_status Poll transaction status. Use in manual mode to check if a pending request was approved. Card details are returned only when the transaction is `spent` and `agent_id` matches. Maps to `GET /status/{tx_id}?agent_id=...`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `transaction_id` | string | Yes | Transaction ID from `request_payment` | | `agent_id` | string | Yes | Your agent identifier (must match the original request to receive card data) | ### get_transactions Retrieve transaction history. Maps to `GET /transactions/{agent}`. Accessible to both user and supervisor agents. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | Yes | Agent identifier | ## Supervisor Agent MCP Tools ### list_pending_transactions (Tier 1+) List all transactions awaiting approval. Maps to `GET /pending`. No parameters. ### approve_transaction (Tier 1+) Approve a pending transaction. Maps to `POST /approve`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `transaction_id` | string | Yes | Transaction ID to approve | ### deny_transaction (Tier 1+) Deny a pending transaction. Budget is refunded. Maps to `POST /deny`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `transaction_id` | string | Yes | Transaction ID to deny | ### set_budget (Tier 2+) Create or update an agent's budget. Maps to `POST /budget/{agent}`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | Yes | Agent identifier | | `limit` | integer | Yes | Budget limit in cents | | `period` | string | No | `cumulative` or `monthly` | ### credit_budget (Tier 2+) Restore consumed budget. Maps to `POST /credit`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `agent_id` | string | Yes | Agent to credit | | `amount` | integer | Yes | Amount in cents | | `reason` | string | Yes | Audit reason | ### update_vault (Tier 3) Replace the vaulted card. Maps to `POST /vault/update`. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `holder_name` | string | Yes | Cardholder name | | `card_number` | string | Yes | Card number (digits only) | | `expiry_date` | string | Yes | MM/YY format | | `cvv` | string | Yes | Security code | | `country` | string | Yes | Two-letter country code | | `zip_code` | string | Yes | Billing ZIP / postal code |