Purser
Developers

Give an agent its account in code.

A TypeScript SDK, an MCP server and a CLI on one engine API. The same rules and the same firewall apply whichever one the agent uses.

Start

One key, three ways in

The owner creates an agent key for one account in the dashboard. The agent uses it with the SDK, as MCP tools, or from the terminal. A refused payment is an answer with reasons, never an exception that slips through.

npm install @purser/sdk
import { Purser, usdg } from "@purser/sdk";

const purser = new Purser({
  baseUrl: process.env.PURSER_ENGINE_URL,
  apiKey: process.env.PURSER_API_KEY,
});

// Pay inside the owner's rules. A refused payment comes back "blocked", with reasons.
const paid = await purser.pay({ to: supplier, amount: usdg("2.50"), memo: "data report" });
if (paid.intent.status === "blocked") console.log(paid.verdict.summary);

// Call an x402 service. Purser pays the 402 from the agent's Safe, up to a maximum.
const res = await purser.x402Fetch(serviceUrl, { maxAmount: usdg("1") });

// Every dollar has a receipt.
const { items } = await purser.accounts.receipts(paid.intent.safe, { limit: 10 });
  • purser_accountBalances, today's spending and what is left
  • purser_rulesThe rules the agent cannot break
  • purser_payPay, with the text that led to it
  • purser_check_paymentAsk the firewall first
  • purser_receiptsEvery payment in and out
  • purser_x402_fetchFetch a URL and pay its 402
  • purser_sellPut a service on sale
  • purser_servicesThe agent's services
  • purser_invoiceInvoice another agent
  • purser_pay_invoiceFund an invoice's escrow
  • purser_pnlProfit and loss
  • Framework guidesClaude Agent SDK, OpenAI Agents SDK, LangChain, ElizaOS

Models on the same account

Any OpenAI-compatible client works. The owner adds the providers; every call is recorded with its tokens and cost, and tagged to a task if you send X-Purser-Task.

models.ts
import OpenAI from "openai";

// Any OpenAI-compatible client, paid from the agent's account.
const models = new OpenAI({
  baseURL: `${process.env.PURSER_ENGINE_URL}/v1`,
  apiKey: process.env.PURSER_API_KEY,
});

const reply = await models.chat.completions.create({
  model: "the-model-id-your-owner-set-up",
  messages: [{ role: "user", content: "Summarise these filings" }],
}, { headers: { "X-Purser-Task": "weekly-brief" } });

Sell over x402

Let the engine run the paywall for an agent's service, or price an endpoint on your own server with the middleware. Either way the money lands in the agent's own Safe.

And the pay button
// Or let the engine run the paywall: the agent puts a service on sale.
const service = await purser.store.create({
  name: "Weekly market report",
  model: "per_call",
  price: usdg("4"),
  upstream: "https://your-worker/report",
});
console.log(service.endpoint); // buyers call this over x402

Signed webhooks

Subscribe a URL in the dashboard under Alerts. Each delivery is signed with HMAC-SHA256 and retried after 5 seconds, 30 seconds and 2 minutes.

webhook.ts
import { verifyWebhook } from "@purser/sdk";

// Every delivery is signed. Reject anything that does not verify.
const ok = await verifyWebhook(process.env.PURSER_WEBHOOK_SECRET, req.headers["purser-signature"], rawBody);
if (!ok) return res.status(401).end();

const event = JSON.parse(rawBody);
if (req.headers["purser-event"] === "intent.blocked") notifyOwner(event);

Test mode and the API

Run everything on a local copy of the chain with test money. The engine answers JSON and live events; the full reference lists every endpoint with a real answer.

# A local copy of Robinhood Chain, Purser deployed on it, the engine and three
# agents with real on-chain activity, in one command:
cd engine && npm run testmode            # a plain local chain
cd engine && npm run testmode -- --fork  # a fork of the live chain

# Or from anywhere in a checkout:
purser testmode --fork