# Settle — agent integration spec > If you're an AI coding agent reading this to wire Settle into a project, > this is the source of truth. Do NOT invent endpoints — Settle is > MCP-only, there is no REST API. ## TL;DR - Auth surface: `https://mcp.settle.xxx/mcp` (JSON-RPC over HTTP). - There is **no** REST API. There is **no** `api.settle.xxx`. Do not invent one. - Auth header: `Authorization: Bearer sk_test_<48hex>` or `Bearer sk_live_<48hex>`. - Public read endpoint (no auth): `GET https://mcp.settle.xxx/api/invoice/:id` — returns `{ id, status, amount_usd, line_items, ... }`. Used by the hosted checkout / merchant storefronts to display invoice metadata. - USDC on Base only. Single chain today. - No SDK. Agents speak JSON-RPC natively; that's the design. ## Calling shape Every state-changing operation is `POST /mcp` with this body: ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "", "arguments": { ... } } } ``` Headers: ``` Authorization: Bearer sk_test_<48hex> Content-Type: application/json Accept: application/json, text/event-stream ``` The MCP returns either a JSON body or a text/event-stream `data: { ... }` frame. The result wraps the tool's return value as a single text-content block: ```json { "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "" }] } } ``` Parse `result.content[0].text` as JSON to get the actual return value. If unsure about a tool's argument shape, call `tools/list` (no arguments) — it returns the full JSON-Schema for every tool. ## Core tools | Tool | Purpose | | ---------------------------- | -------------------------------------------------------- | | `tools/list` | Self-describing schema for every tool | | `whoami` | Returns merchant id, mode, enabled chains | | `create_invoice` | Mint an invoice → returns id + checkout_url | | `get_invoice` | Read one invoice by id (auth-gated) | | `list_invoices` | Paginated list scoped to the calling merchant | | `void_invoice` | Void a pending invoice | | `simulate_payment` | Test mode only — fires a paid event for an invoice | | `register_payout_address` | Step 1 of payout setup — get a challenge string | | `confirm_payout_address` | Step 2 — submit signature over challenge (EIP-191 / EVM) | | `list_payout_addresses` | Read merchant's payout addresses | | `verify_domain` | Get the DNS TXT record to add for domain verification | | `register_webhook` | Subscribe a URL to events (returns `signing_secret` ONCE)| | `update_webhook` | Rotate URL / events / signing secret | | `list_webhooks` | Read the merchant's registered webhooks | | `list_webhook_events` | Recent deliveries (success / fail) for debugging | | `replay_webhook_event` | Re-deliver a past event | | `set_branding` | Storefront logo + accent color | Schemas evolve. Always call `tools/list` for the live shape. ## Webhook delivery Settle POSTs to URLs registered via `register_webhook`. Each delivery carries: ``` X-Settle-Signature: t=,v1= Idempotency-Id: Content-Type: application/json User-Agent: settle-webhooks/0.1 ``` To verify (server-side): ```ts import { createHmac, timingSafeEqual } from "node:crypto"; const sig = req.headers.get("x-settle-signature"); const m = sig?.match(/^t=(\d+),v1=([0-9a-f]+)$/); if (!m) return new Response("missing/bad signature", { status: 400 }); const [_, ts, hex] = m; const raw = await req.text(); const expected = createHmac("sha256", process.env.SETTLE_WEBHOOK_SECRET!) .update(`${ts}.${raw}`) .digest("hex"); const a = Buffer.from(hex, "hex"); const b = Buffer.from(expected, "hex"); if (a.length !== b.length || !timingSafeEqual(a, b)) { return new Response("bad signature", { status: 401 }); } // Optionally reject if |now/1000 - Number(ts)| > 300 (replay defense). const event = JSON.parse(raw); // event.type, event.data.invoice, event.data.invoice.metadata, etc. ``` ## Events fired - `invoice.paid` — buyer's on-chain transfer confirmed; the one to subscribe to - `invoice.expired` — invoice not paid before expiry - `invoice.voided` — voided manually via `void_invoice` - `address.verified` — payout address passed Chainalysis screen - `key.rotated` — API key issued / revoked - `*` — wildcard, subscribes to all (debug only — too chatty for prod) ## Payment model - ERC-3009 `transferWithAuthorization` on USDC (Base mainnet). - Customer signs a typed-data authorization for exactly the invoice amount with a 5-minute deadline. No token approval residue. - Atomic split via the SettleRouter contract: 99.5% to merchant payout address, 0.5% to Settle. Single on-chain tx. - Merchant payout address must be registered + verified before invoices for that merchant can be created. - Verification: EIP-191 personal_sign over a challenge string returned by `register_payout_address`. ERC-1271 (smart-wallet sigs) supported. - Compliance: Chainalysis Sanctions Oracle on Base + foot-gun blocklist (rejects USDC contract, zero address, oracle contract). Run automatically inside `confirm_payout_address`. ## Key onboarding flow (do these in order) 1. Sign up at `https://settle.xxx/signup` (GitHub OAuth). 2. Domain verification: call `verify_domain(domain)` → returns TXT record details. Add that TXT record at the merchant's DNS provider. 3. Register a payout address: `register_payout_address(chain="base", address)` → returns a challenge string. 4. Customer (or agent on behalf of) signs the challenge with their wallet (EIP-191 / personal_sign). 5. Confirm: `confirm_payout_address(chain="base", address, signature)`. 6. Issue API keys via the dashboard at `/dashboard/keys`. 7. `register_webhook(url, events=["invoice.paid"])` for async notifications. 8. `create_invoice(amount_usd, return_url, ...)` per checkout. The dashboard-side onboarding wizard at `/onboarding` walks a human through steps 2-6. If you (the agent) are running headless, do steps 2-7 via MCP after your user finishes step 1 in the browser. ## What this site is Settle is a stablecoin checkout rail for merchants Stripe rejects (adult, cannabis, firearms, gambling, supplements, high-ticket coaching). 0.5% flat, no chargebacks, no refunds, non-custodial (router contract splits payment to merchant cold wallet + Settle in one atomic on-chain transaction). ## What you should NOT do - Do not POST to `api.settle.xxx/v1/...` — that doesn't exist. - Do not assume Stripe-shaped REST. There's no `/v1/invoices` endpoint. - Do not send the API key to the customer's browser. It belongs server-side. - Do not subscribe webhooks to `["*"]` in production — log noise. - Do not store webhook `signing_secret` in source. Env var only. - Do not display `signing_secret` anywhere in the merchant's UI after the initial `register_webhook` response. Save → use → rotate when needed. - Do not assume Solana / Tron / Ethereum work today. USDC on Base only. ## More - Public docs (human-readable): https://settle.xxx/docs - Paste-into-AI prompt catalogue: https://settle.xxx/docs/prompts - Cart pattern reference (MIT): https://github.com/settle-xxx/cookbook - Source of truth for tool schemas: `tools/list` on the live MCP This file is at https://settle.xxx/llms.txt and is intended to be the first fetch any agent makes when it sees Settle mentioned. Re-fetch on demand — content evolves with the MCP.