Documentation

WEIR docs

One MCP endpoint, one REST API, two SDKs. Everything an agent needs to hold and spend USDC within the limits you set.

OAuth 2.1 with PKCE. Never choose “No auth” in a connector.

Connect

Three ways in. One grant model.

A connector, an OAuth client or an API key all end up with the same scoped grant: which agents, which tools, which approval mode.

MCP connector

  1. 01Paste the endpoint into your model's connector settings.
  2. 02Name it WEIR and choose OAuth.
  3. 03Sign in with your passkey and scope the grant per agent.

Clients that read a project config file take the same endpoint over streamable HTTP.

.mcp.json
{
  "mcpServers": {
    "weir": {
      "type": "http",
      "url": "https://api.weir.sh/mcp"
    }
  }
}

OAuth 2.1

Authorization code with PKCE (S256). Public clients are fine. Discovery documents:

  • /.well-known/oauth-authorization-server
  • /.well-known/oauth-protected-resource
  • mcp
  • offline_access
  • payments:write
  • read
discovery
GET /.well-known/oauth-authorization-serverHost: api.weir.sh{"issuer": "https://api.weir.sh","code_challenge_methods_supported": ["S256"],"grant_types_supported": ["authorization_code","refresh_token"],"scopes_supported": ["mcp", "offline_access","payments:write", "read"]}

SDK & CLI

Typed clients for TypeScript (@weir/sdk) and Python (weir) over the same REST surface. API keys look like weir_sk_test_… and are stored hashed.

The CLI signs in with the same OAuth grant, so headless agents get the same passkey approvals as a connector.

pay.ts
import { Weir } from "@weir/sdk";

const weir = new Weir({ apiKey: process.env.WEIR_API_KEY! });

const agent = await weir.agents.create({ name: "researcher" });
await weir.payments.pay({
  agentId: agent.id,
  to: "0x9a1c…f2e0",
  amountMicro: "1000000", // $1.00
});
const bal = await weir.agents.balance(agent.id);
pay.py
import os
from weir import Weir

weir = Weir(api_key=os.environ["WEIR_API_KEY"])
agent = weir.agents.create(name="researcher")
weir.payments.pay(
    agent_id=agent.id,
    to="0x9a1c…f2e0",
    amount_micro="1000000",  # $1.00
)
shell
$ npm i -g @weir/sdk
$ weir login          # OAuth 2.1 + PKCE, passkey approvals
$ weir agents list

Reference

Eighteen MCP tools.

Every tool routes the same way: simulate → policy check → compliance screen → sign. Scopes are enforced per tool; two of them need a fresh passkey.

Agents

  • create_agentpayments:write

    Create an agent wallet under a policy. Mints an ERC-8004 identity when asked.

  • get_walletread

    The agent's Arc address, chain id and the USDC token address.

  • get_balanceread

    ERC-20 (6dp) and native (18dp) views, plus available after holds.

  • get_agent_reputationread

    ERC-8004 score, tier and the limit multiplier it earns.

Payments

  • simulate_paymentread

    Dry-run policy and compliance. Returns the ordered rule evaluation and the fee.

  • paypayments:write

    Send USDC from an agent wallet. Settles, pauses for approval, or is denied.

  • pay_x402payments:write

    Answer a 402 challenge within max_amount_micro. Verify → settle → deliver.

  • list_transactionsread

    Page through payments and their ledger legs for an agent.

Escrow

  • create_escrowpayments:write

    Lock USDC in EscrowVault for a seller with a deadline.

  • release_escrowpayments:writepasskey

    Release to the seller. The owner's passkey is required.

  • dispute_escrowpayments:write

    Open a dispute and an evidence window on a funded escrow.

Streams and subscriptions

  • start_streampayments:write

    Open a per-second USDC stream against a deposit.

  • withdraw_streampayments:write

    Transfer min(accrued, remaining) to the recipient.

  • create_subscriptionpayments:write

    Recurring charge under an AP2 mandate with a cap and an expiry.

  • cancel_subscriptionpayments:write

    Stop the schedule and void the mandate.

Policy and approvals

  • get_policyread

    The effective policy: mode, caps, allow and block lists, reputation floor.

  • set_policypayments:writepasskey

    Propose a policy change. Applies after a passkey step-up and mirrors on-chain.

  • request_approvalpayments:write

    Ask the owner to approve an action above policy. Expires after ten minutes.

tools/pay.schema.json
{
  "name": "pay",
  "description": "Send USDC on Arc from an agent wallet, subject to policy and compliance.",
  "inputSchema": {
    "type": "object",
    "required": ["agent_id", "to", "amount_micro"],
    "properties": {
      "agent_id":        { "type": "string", "format": "uuid" },
      "to":              { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$" },
      "amount_micro":    { "type": "string", "description": "micro-USDC integer (6dp)" },
      "idempotency_key": { "type": "string" }
    }
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "status":     { "enum": ["settled", "pending_approval", "policy_denied", "compliance_blocked"] },
      "tx_hash":    { "type": ["string", "null"] },
      "payment_id": { "type": "string" }
    }
  }
}
tools/pay_x402.schema.json
{
  "name": "pay_x402",
  "description": "Pay an x402-protected resource within an explicit spend cap.",
  "inputSchema": {
    "type": "object",
    "required": ["agent_id", "resource_url", "max_amount_micro"],
    "properties": {
      "agent_id":         { "type": "string", "format": "uuid" },
      "resource_url":     { "type": "string", "format": "uri" },
      "max_amount_micro": { "type": "string" }
    }
  }
}

Error shape

Every tool returns the same machine-readable error. details.rule names the failing policy rule so the agent can explain itself.

Amounts are always micro-USDC integer strings. There are no floats anywhere on the wire.

error
{
  "code": "policy_denied",
  "message": "Per-transaction cap is $50.00",
  "details": { "rule": "max_per_tx" }
}

Concepts

Request lifecycle.

Submit once, then poll or subscribe. Every payment moves through the same states and stops at exactly one of them.

  1. Pending
  2. Awaiting approval
  3. Settling
  4. SettledFailedPolicy deniedCompliance blocked
StatusMeaningWhat happens next
PendingpendingIntent recorded. Policy and compliance are being evaluated.Poll the payment or subscribe to /v1/stream.
Awaiting approvalpending_approvalAbove the approval threshold, or the mode is always-ask. An approval exists and expires after ten minutes.The owner approves with a passkey and the intent resumes as settling. Denial or expiry ends in failed.
SettlingsettlingSigned and broadcast. Waiting for the on-chain log.Sub-second on Arc. Safe to retry with the same idempotency key.
Settledsettled · terminalBoth logs indexed, two ledger legs posted, balance updated.Terminal. tx_hash is set.
Failedfailed · terminalSettlement did not complete; failure_code says why. Nothing was delivered.Terminal. Retry with a new idempotency key.
Policy deniedpolicy_denied · terminalA rule failed (details.rule). Nothing was signed.Terminal. Change the amount, the counterparty or the policy.
Compliance blockedcompliance_blocked · terminalSender or counterparty screening blocked the transfer before broadcast.Terminal. Not retryable.

Reference

Error taxonomy.

Fifteen codes, one body shape: { code, message, details, request_id }. Quote the request_id when you write to support.

CodeWhenRetry
validation_errorMalformed input: sub-micro amounts, bad addresses, threshold above the per-tx cap.Fix the request.
unauthorizedMissing or expired API key, token or passkey step-up.Re-authenticate.
forbiddenScope missing, or a human denied the approval.No.
not_foundUnknown id.No.
rate_limitedToo many requests for this key.After Retry-After.
idempotency_conflictSame idempotency key with a different body (409).Use a new key.
policy_deniedA policy rule failed; details.rule names it.Adjust the amount, counterparty or policy.
compliance_blockedScreening blocked the sender or the counterparty.No.
insufficient_balanceAvailable balance is below amount plus fee.Fund the agent.
settlement_failedThe on-chain transfer did not complete.With a new idempotency key.
facilitator_unavailableThe x402 facilitator could not verify or settle.With backoff.
nonce_reusedThe EIP-3009 nonce was already spent.Sign a fresh authorization.
authorization_expiredvalidBefore passed, or the approval window closed.Sign or request again.
upstream_circle_errorCircle's wallet API returned an error.With backoff.
indexer_staleIndexer lag is above the threshold; balances may be behind.Wait; reads are still served.

Reference

Webhooks.

Signed with HMAC-SHA256 over the timestamp and the raw body. Verify the signature, check the window, dedupe on the delivery id.

Signature scheme

signed
{timestamp}.{rawBody}
algorithm
HMAC-SHA256, hex digest
header
Weir-Signature: t=<unix>,v1=<hex hmac>
secret
whsec_… (shown once at creation)
window
Reject when |now − t| > 300 s.
retries
1m · 5m · 30m · 2h · 12h, eight attempts, then the delivery is marked dead.
replay
Weir-Delivery-Id is unique per delivery. Receivers must dedupe.
delivery
POST https://hooks.example.com/weir
Content-Type: application/json
Weir-Signature: t=1789387200,v1=5f1c2a…9e0b
Weir-Delivery-Id: dlv_01j7v8m2k4q9

{
  "id": "evt_01j7v8m2k4qa",
  "type": "payment.settled",
  "data": {
    "id": "pay_01j7v8m1x0c3",
    "status": "settled",
    "tx_hash": "0x4be1…a07c"
  }
}
verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWeirSignature(rawBody: string, header: string, secret: string): boolean {
  const parts = new Map(header.split(",").map((kv) => kv.split("=") as [string, string]));
  const t = Number(parts.get("t"));
  const v1 = parts.get("v1") ?? "";
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; // replay window

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Event catalog

  • payment.settled
  • payment.failed
  • escrow.created
  • escrow.released
  • escrow.refunded
  • escrow.disputed
  • stream.withdrawn
  • subscription.charged
  • subscription.failed
  • subscription.canceled
  • approval.requested
  • dispute.opened
  • dispute.resolved

Ready to connect an agent?

Start on Arc testnet (chain 5042002): fund from the faucet, connect a model, watch the ledger.