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
- 01Paste the endpoint into your model's connector settings.
- 02Name it WEIR and choose OAuth.
- 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.
{
"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
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.
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);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
)$ npm i -g @weir/sdk
$ weir login # OAuth 2.1 + PKCE, passkey approvals
$ weir agents listReference
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:writeCreate an agent wallet under a policy. Mints an ERC-8004 identity when asked.
get_walletreadThe agent's Arc address, chain id and the USDC token address.
get_balancereadERC-20 (6dp) and native (18dp) views, plus available after holds.
get_agent_reputationreadERC-8004 score, tier and the limit multiplier it earns.
Payments
simulate_paymentreadDry-run policy and compliance. Returns the ordered rule evaluation and the fee.
paypayments:writeSend USDC from an agent wallet. Settles, pauses for approval, or is denied.
pay_x402payments:writeAnswer a 402 challenge within max_amount_micro. Verify → settle → deliver.
list_transactionsreadPage through payments and their ledger legs for an agent.
Escrow
create_escrowpayments:writeLock USDC in EscrowVault for a seller with a deadline.
release_escrowpayments:writepasskeyRelease to the seller. The owner's passkey is required.
dispute_escrowpayments:writeOpen a dispute and an evidence window on a funded escrow.
Streams and subscriptions
start_streampayments:writeOpen a per-second USDC stream against a deposit.
withdraw_streampayments:writeTransfer min(accrued, remaining) to the recipient.
create_subscriptionpayments:writeRecurring charge under an AP2 mandate with a cap and an expiry.
cancel_subscriptionpayments:writeStop the schedule and void the mandate.
Policy and approvals
get_policyreadThe effective policy: mode, caps, allow and block lists, reputation floor.
set_policypayments:writepasskeyPropose a policy change. Applies after a passkey step-up and mirrors on-chain.
request_approvalpayments:writeAsk the owner to approve an action above policy. Expires after ten minutes.
{
"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" }
}
}
}{
"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.
{
"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.
- Pending
- Awaiting approval
- Settling
- SettledFailedPolicy deniedCompliance blocked
| Status | Meaning | What happens next |
|---|---|---|
| Pendingpending | Intent recorded. Policy and compliance are being evaluated. | Poll the payment or subscribe to /v1/stream. |
| Awaiting approvalpending_approval | Above 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. |
| Settlingsettling | Signed and broadcast. Waiting for the on-chain log. | Sub-second on Arc. Safe to retry with the same idempotency key. |
| Settledsettled · terminal | Both logs indexed, two ledger legs posted, balance updated. | Terminal. tx_hash is set. |
| Failedfailed · terminal | Settlement did not complete; failure_code says why. Nothing was delivered. | Terminal. Retry with a new idempotency key. |
| Policy deniedpolicy_denied · terminal | A rule failed (details.rule). Nothing was signed. | Terminal. Change the amount, the counterparty or the policy. |
| Compliance blockedcompliance_blocked · terminal | Sender 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.
| Code | When | Retry |
|---|---|---|
validation_error | Malformed input: sub-micro amounts, bad addresses, threshold above the per-tx cap. | Fix the request. |
unauthorized | Missing or expired API key, token or passkey step-up. | Re-authenticate. |
forbidden | Scope missing, or a human denied the approval. | No. |
not_found | Unknown id. | No. |
rate_limited | Too many requests for this key. | After Retry-After. |
idempotency_conflict | Same idempotency key with a different body (409). | Use a new key. |
policy_denied | A policy rule failed; details.rule names it. | Adjust the amount, counterparty or policy. |
compliance_blocked | Screening blocked the sender or the counterparty. | No. |
insufficient_balance | Available balance is below amount plus fee. | Fund the agent. |
settlement_failed | The on-chain transfer did not complete. | With a new idempotency key. |
facilitator_unavailable | The x402 facilitator could not verify or settle. | With backoff. |
nonce_reused | The EIP-3009 nonce was already spent. | Sign a fresh authorization. |
authorization_expired | validBefore passed, or the approval window closed. | Sign or request again. |
upstream_circle_error | Circle's wallet API returned an error. | With backoff. |
indexer_stale | Indexer 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.
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"
}
}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.