Docs / REST API
REST API.
Same surface as the MCP, just over HTTP. Bearer-token auth. JSON in, JSON out. Twenty-four endpoints across orientation, onboarding, invoices, webhooks, and keys.
Base URL.
https://api.settle.xxx. All endpoints are under /v1. We version every breaking change with a major bump and run old versions for at least 12 months.
Authentication.
Bearer-token. Pass an Authorization: Bearer … header on every request. Two key flavours.
- Secret keys —
sk_live_…,sk_test_…. Server-side only. Full power. Auto-rotated every 90 days with a 7-day grace window where both old and new work. - Publishable keys —
pk_live_…,pk_test_…. Client-safe. Origin-bound to your verified domain. Limited toPOST /v1/invoicesandGET /v1/invoices/{id}.
Missing or invalid keys return 401. A revoked key returns 401 on the first request after revocation — there is no grace.
Errors.
Non-2xx responses always carry a JSON body shaped like:
{
"error": {
"type": "ofac_blocked",
"message": "Payer address is on a sanctions list. Checkout will not load.",
"param": null,
"request_id": "req_01J0AHFG..."
}
}type is a stable machine-readable slug. The full set is small and documented per endpoint. param is set when the failure is tied to a specific request field. message is human-readable; do not parse it.
| Status | Meaning |
|---|---|
400 | Malformed request, missing required field. |
401 | Missing, invalid, or revoked API key. |
403 | Key doesn’t have permission for this endpoint (e.g. simulate_payment with a live key). |
404 | Resource doesn’t exist or isn’t visible to you. |
409 | Conflict — e.g. voiding an already-paid invoice, confirming a payout address that wasn’t registered. |
422 | Policy denial — OFAC block, hard cap exceeded, signature didn’t cover the challenge. |
429 | Rate limited. Back off and retry — see rate limits. |
5xx | Our problem. Retry with exponential backoff. |
Idempotency.
All POST mutating endpoints accept an Idempotency-Key header. We store the original response for 24 hours; replays with the same key return the same body, even if the original request created an invoice or rotated a secret. Use a UUID per logical operation.
curl -X POST https://api.settle.xxx/v1/invoices \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 8f1a3b2c-7d4e-4f6a-9b8c-1e2d3f4a5b6c" \
-H "Content-Type: application/json" \
-d '{ "amount_usd": 49.00 }'For POST /v1/invoices only, the key may also be passed as a body field (idempotency_key) to mirror the MCP tool argument shape. The header takes precedence if both are present.
Rate limits.
100 req/s per key, burst 200. The 429 response includes a Retry-After header. Higher limits available on request — see Security / Anti-abuse limits.
Each endpoint below maps 1:1 to an MCP tool. Argument names match.
Orientation.
Three endpoints that tell the agent (or you) what state the merchant is in and what to do next. Read these first.
GET /v1/get_started
Returns the onboarding checklist for the current merchant — which steps are done, which are open, the exact next endpoint to call. Always start here. Works without auth, in which case it returns the obtain-API-key flow.
curl https://api.settle.xxx/v1/get_started \
-H "Authorization: Bearer sk_test_..."{
"steps": [
{ "name": "register_payout_address", "done": false,
"next_tool": "register_payout_address" },
{ "name": "verify_domain", "done": false },
{ "name": "set_branding", "done": false },
{ "name": "register_webhook", "done": false },
{ "name": "create_first_invoice", "done": false }
],
"next_action": "Next: Register a payout address on at least one chain..."
}GET /v1/whoami
Returns the merchant id, display name, mode (test / live), the chains with a verified payout address, and the verified domain. Use it to confirm context before anything destructive.
curl https://api.settle.xxx/v1/whoami \
-H "Authorization: Bearer sk_test_..."{
"merchant_id": "mer_01HZX...",
"display_name": "Acme",
"mode": "test",
"enabled_chains": ["base"],
"verified_domain": "acme.com"
}GET /v1/integration_patterns/{framework}
Returns a working server-route snippet, webhook handler, env vars, and install command for the merchant’s framework. Frameworks: nextjs, hono, fastapi, express.
| Param | Type | Required | Description |
|---|---|---|---|
framework | enum | yes | One of nextjs, hono, fastapi, express. |
curl "https://api.settle.xxx/v1/integration_patterns/nextjs" \
-H "Authorization: Bearer sk_test_..."{
"framework": "nextjs",
"install": "bun add @settle/sdk",
"env": ["SETTLE_API_KEY", "SETTLE_WEBHOOK_SECRET"],
"files": [
{ "path": "app/api/checkout/route.ts", "code": "..." },
{ "path": "app/api/webhooks/settle/route.ts", "code": "..." }
]
}Onboarding.
Seven endpoints that take a merchant from zero to able-to-be-paid: register and verify payout addresses, verify domain, set branding, opt into USDT-Ethereum.
POST /v1/payout_addresses
Step 1 of payout setup. Registers a cold-wallet payout address for a chain and returns a challenge string the merchant must sign with that address’s private key. Funds settle directly to this address — Settle is non-custodial.
| Param | Type | Required | Description |
|---|---|---|---|
chain | enum | yes | base. |
address | string | yes | The payout address on that chain. |
curl -X POST https://api.settle.xxx/v1/payout_addresses \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"chain": "base",
"address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e"
}'{
"challenge": "Settle wants to verify that you own 0xA0Cf... on base. Token: a3f1c9...",
"instructions": "Sign the challenge with the private key for 0xA0Cf... Then call POST /v1/payout_addresses/base/confirm."
}POST /v1/payout_addresses/{chain}/confirm
Step 2 of payout setup. Verifies the signature over the challenge from the previous call. Once verified, the chain becomes a live payment option on every invoice.
| Param | Type | Required | Description |
|---|---|---|---|
address | string | yes | Must match the pending registration for this chain. |
signature | string | yes | Signature over the challenge string via personal_sign (EIP-191). |
curl -X POST https://api.settle.xxx/v1/payout_addresses/base/confirm \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e",
"signature": "0x4c8e2d..."
}'{
"verified": true,
"chain": "base",
"address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e",
"message": "base payout verified. This chain is now active on every invoice."
}422 if the signature is empty or doesn’t cover the challenge. 409 if the address doesn’t match the pending registration — re-register first.
GET /v1/payout_addresses
Lists every payout address registered, with verification status per chain.
curl https://api.settle.xxx/v1/payout_addresses \
-H "Authorization: Bearer sk_test_..."{
"addresses": [
{ "chain": "base", "address": "0xA0Cf...", "verified": true, "registered_at": "..." }
]
}POST /v1/domains
Begins domain verification. Returns the TXT record to add to DNS. Verification completes automatically about 30 seconds after propagation. Required before publishable keys can be origin-bound.
| Param | Type | Required | Description |
|---|---|---|---|
domain | string | yes | The bare apex, e.g. acme.com. No protocol. |
curl -X POST https://api.settle.xxx/v1/domains \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "domain": "acme.com" }'{
"record_type": "TXT",
"name": "_settle-verify",
"value": "v=1;token=a3f1c9...",
"instructions": "Add a TXT record at _settle-verify.acme.com. Verification completes ~30s after propagation."
}POST /v1/usdt_ethereum/enable
Opts the merchant into accepting USDT on Ethereum mainnet. Only offered to buyers when the invoice is at or above min_invoice_usd. Floor is $500 — Ethereum gas costs $5–25, so smaller invoices are uneconomic.
| Param | Type | Required | Description |
|---|---|---|---|
min_invoice_usd | number | no | Default 1000, floor 500. |
curl -X POST https://api.settle.xxx/v1/usdt_ethereum/enable \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "min_invoice_usd": 1000 }'{
"enabled": true,
"min_invoice_usd": 1000,
"message": "USDT on Ethereum is now offered on invoices ≥ $1000."
}409 if the merchant has no verified Ethereum payout address yet — register and confirm chain: "ethereum" first.GET /v1/branding
Returns the merchant’s current hosted-checkout branding (logo URL and accent hex).
curl https://api.settle.xxx/v1/branding \
-H "Authorization: Bearer sk_test_..."{
"logo_url": "https://acme.com/logo.svg",
"accent_hex": "#0E7C66"
}PUT /v1/branding
Sets the logo URL and/or accent colour shown on the hosted checkout page (pay.settle.xxx). Pass either or both. Accent is a 6-digit hex like #0E7C66.
| Param | Type | Required | Description |
|---|---|---|---|
logo_url | string (url) | no | HTTPS URL to a square SVG or PNG. |
accent_hex | string | no | 6-digit hex, e.g. #0E7C66. |
curl -X PUT https://api.settle.xxx/v1/branding \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"logo_url": "https://acme.com/logo.svg",
"accent_hex": "#0E7C66"
}'{
"ok": true,
"branding": { "logo_url": "https://acme.com/logo.svg", "accent_hex": "#0E7C66" }
}Invoices.
The hot path. Five endpoints to create a payable URL, poll its status, and (in test mode) walk the full happy path without sending real funds.
POST /v1/invoices
Creates a payment invoice and returns a checkout_url to send the buyer to. The available chains and currencies are picked server-side from the merchant’s verified payout addresses and their USDT-Ethereum opt-in. Amounts are USD; the buyer pays the same number in USDC or USDT (1:1).
| Param | Type | Required | Description |
|---|---|---|---|
amount_usd | number | yes | Positive, max 500000 per transaction. |
accepted_currencies | string[] | no | Filter, e.g. ["usdc-base", "usdc"]. Defaults to all enabled. |
customer_email | string | no | Used for receipts only. Never shared on-chain. |
description | string | no | Up to 500 chars. Shown on the checkout page. |
metadata | object | no | Up to 50 string-only keys. Echoed on every webhook. |
return_url | string (url) | no | Buyer redirected here after pay. |
cancel_url | string (url) | no | Buyer redirected here on cancel. |
expires_in | number (sec) | no | Default 3600, max 604800 (7d). |
idempotency_key | string | no | Body-level alternative to the Idempotency-Key header. |
curl -X POST https://api.settle.xxx/v1/invoices \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 8f1a-3b2c-..." \
-H "Content-Type: application/json" \
-d '{
"amount_usd": 49.00,
"customer_email": "buyer@example.com",
"description": "Pro plan, monthly",
"metadata": { "order_id": "ord_123" },
"return_url": "https://acme.com/orders/success",
"cancel_url": "https://acme.com/cart"
}'{
"id": "inv_01HZX9V0K1Q3Y2T7M3N4D5R8S0",
"checkout_url": "https://pay.settle.xxx/c/inv_01HZX9V0K1Q3Y2T7M3N4D5R8S0",
"status": "pending",
"expires_at": "2026-04-27T13:30:00Z",
"amount_usd": 49.00,
"payment_options": [
{ "currency": "USDC", "chain": "base", "amount": "49.000000", "network_fee_estimate_usd": 0.01 }
]
}409 if no verified payout chain. 422 if accepted_currencies excludes every verified chain or the amount is below the USDT-Ethereum floor for an Ethereum-only merchant.GET /v1/invoices/{id}
Fetches a single invoice. Use this to poll status — pending → paid | expired | voided. Includes payment_tx (chain, hash, payer) once paid. Webhooks are the preferred path; polling is the fallback.
curl https://api.settle.xxx/v1/invoices/inv_01HZX9V0K1Q3Y2T7M3N4D5R8S0 \
-H "Authorization: Bearer sk_live_..."{
"id": "inv_01HZX9V0K1Q3Y2T7M3N4D5R8S0",
"status": "paid",
"amount_usd": 49.00,
"paid_at": "2026-04-27T12:32:14Z",
"payment_tx": {
"chain": "base",
"hash": "0x9ad3b1...",
"payer": "0x73f4a2..."
},
"checkout_url": "https://pay.settle.xxx/c/inv_01HZX9V0K1Q3Y2T7M3N4D5R8S0"
}GET /v1/invoices
Lists invoices for the current merchant. Filters compose with AND.
| Query param | Type | Description |
|---|---|---|
status | enum | pending, paid, expired, voided. |
after | ISO8601 | Created at or after this timestamp. |
before | ISO8601 | Created at or before this timestamp. |
customer_email | string | Exact match. |
metadata_key | string | Returns invoices whose metadata contains this key. |
curl "https://api.settle.xxx/v1/invoices?status=paid&customer_email=buyer@example.com" \
-H "Authorization: Bearer sk_live_..."{
"invoices": [
{ "id": "inv_01HZX...", "status": "paid", "amount_usd": 49.00, "paid_at": "..." },
{ "id": "inv_01HZW...", "status": "paid", "amount_usd": 99.00, "paid_at": "..." }
],
"count": 2
}POST /v1/invoices/{id}/void
Voids a pending invoice so the buyer can no longer pay it. No-op if already paid — Settle has no refunds. Once paid, funds are in the merchant’s cold wallet. Fires invoice.voided.
curl -X POST https://api.settle.xxx/v1/invoices/inv_01HZX.../void \
-H "Authorization: Bearer sk_live_..."{
"ok": true,
"status": "voided",
"invoice_id": "inv_01HZX..."
}POST /v1/invoices/{id}/simulate_payment
Test mode only. Marks the invoice paid and fires invoice.paid so you can wire up your webhook handler end-to-end without sending real funds. Fails if the merchant is in live mode or the invoice is not pending.
curl -X POST https://api.settle.xxx/v1/invoices/inv_01HZX.../simulate_payment \
-H "Authorization: Bearer sk_test_..."{
"ok": true,
"invoice": {
"id": "inv_01HZX...",
"status": "paid",
"paid_at": "2026-04-27T12:32:14Z",
"payment_tx": { "chain": "base", "hash": "0xtest...", "payer": "0xtestpayer..." }
},
"events_fired": [ /* WebhookEvent[] */ ]
}403 if the calling key issk_live_. 409 if the invoice is notpending.
Webhooks.
Six endpoints to register hooks, rotate their secrets, and inspect or replay individual deliveries. Delivery is HMAC-SHA256 signed, retried with exponential backoff up to 24 hours.
POST /v1/webhooks
Registers an HTTPS endpoint. Returns a signing_secret shown only once — store it as SETTLE_WEBHOOK_SECRET in the merchant’s app and use it to verify the SETTLE-SIGNATURE header. Pass ["*"] to subscribe to everything.
| Param | Type | Required | Description |
|---|---|---|---|
url | string (https url) | yes | Plain HTTPS only — http:// is rejected. |
events | string[] | yes | One or more of invoice.created, invoice.paid, invoice.expired, invoice.voided, invoice.underpaid, payout.completed, domain.verified, address.verified, key.rotated, key.suspicious_use, or "*". |
curl -X POST https://api.settle.xxx/v1/webhooks \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://acme.com/webhooks/settle",
"events": ["invoice.paid", "invoice.expired"]
}'{
"id": "wh_01HZX...",
"url": "https://acme.com/webhooks/settle",
"events": ["invoice.paid", "invoice.expired"],
"signing_secret": "whsec_4f8c2d...",
"created_at": "2026-04-27T12:00:00Z",
"message": "Store signing_secret as SETTLE_WEBHOOK_SECRET. It will not be shown again."
}GET /v1/webhooks
Lists every webhook for the current merchant. Signing secrets are masked — rotate via PATCH /v1/webhooks/{id}.
curl https://api.settle.xxx/v1/webhooks \
-H "Authorization: Bearer sk_live_..."{
"webhooks": [
{
"id": "wh_01HZX...",
"url": "https://acme.com/webhooks/settle",
"events": ["invoice.paid", "invoice.expired"],
"signing_secret_masked": "whsec_••••8d4f",
"created_at": "2026-04-27T12:00:00Z"
}
]
}PATCH /v1/webhooks/{id}
Updates a webhook’s URL and/or event list, and/or rotates its signing secret. When rotate_secret is true the new secret is returned once — update SETTLE_WEBHOOK_SECRET immediately, the old one is revoked.
| Param | Type | Required | Description |
|---|---|---|---|
url | string (https url) | no | Replace the destination URL. |
events | string[] | no | Replace the subscribed events list. |
rotate_secret | boolean | no | If true, issue and return a new signing_secret. |
curl -X PATCH https://api.settle.xxx/v1/webhooks/wh_01HZX... \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"events": ["invoice.paid", "invoice.expired", "invoice.voided"],
"rotate_secret": true
}'{
"ok": true,
"id": "wh_01HZX...",
"url": "https://acme.com/webhooks/settle",
"events": ["invoice.paid", "invoice.expired", "invoice.voided"],
"signing_secret": "whsec_9b7e1a...",
"message": "Update SETTLE_WEBHOOK_SECRET in the app — old secret is now revoked."
}DELETE /v1/webhooks/{id}
Permanently deletes a webhook. In-flight retries for events already enqueued will still attempt delivery once, then stop.
curl -X DELETE https://api.settle.xxx/v1/webhooks/wh_01HZX... \
-H "Authorization: Bearer sk_live_..."{ "ok": true, "deleted": "wh_01HZX..." }GET /v1/webhook_events
Lists recent webhook deliveries with status, response code, and the full payload that was POSTed. Useful for debugging when a merchant says “I’m not getting webhooks.”
| Query param | Type | Description |
|---|---|---|
webhook_id | string | Limit to one webhook. |
status | enum | delivered, failed, pending. |
curl "https://api.settle.xxx/v1/webhook_events?webhook_id=wh_01HZX...&status=failed" \
-H "Authorization: Bearer sk_live_..."{
"events": [
{
"id": "evt_01HZX...",
"webhook_id": "wh_01HZX...",
"event": "invoice.paid",
"status": "failed",
"response_code": 502,
"attempts": 4,
"delivered_at": null,
"payload": { /* the body that was POSTed */ }
}
]
}POST /v1/webhook_events/{id}/replay
Re-fires a webhook event with the same payload. Useful if the merchant’s handler was down or buggy when the original delivery happened.
curl -X POST https://api.settle.xxx/v1/webhook_events/evt_01HZX.../replay \
-H "Authorization: Bearer sk_live_..."{
"ok": true,
"replayed_as": "evt_01HZY...",
"original_id": "evt_01HZX..."
}API keys.
Three endpoints to create, list, and revoke secret API keys. The full key is shown ONCE at creation. After that, only the prefix and last 4 are returned.
POST /v1/api_keys
Creates a new secret API key. mode: "test" returns sk_test_…; mode: "live" returns sk_live_…. Store it in the merchant’s secret manager (Vercel/Netlify env vars preferred).
| Param | Type | Required | Description |
|---|---|---|---|
name | string | yes | 1–80 chars. Shown in the dashboard for identification. |
mode | enum | yes | test or live. |
curl -X POST https://api.settle.xxx/v1/api_keys \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Production server",
"mode": "live"
}'{
"id": "key_01HZX...",
"name": "Production server",
"mode": "live",
"key": "sk_live_4f8c2d9b7e1a3c5f6d8e2a1b...",
"created_at": "2026-04-27T12:00:00Z",
"message": "This is the only time the full key is shown. Store it as SETTLE_API_KEY."
}GET /v1/api_keys
Lists every API key for this merchant. Values are masked — only the prefix and last 4 are returned.
curl https://api.settle.xxx/v1/api_keys \
-H "Authorization: Bearer sk_live_..."{
"keys": [
{
"id": "key_01HZX...",
"name": "Production server",
"mode": "live",
"masked": "sk_live_••••2a1b",
"created_at": "2026-04-27T12:00:00Z",
"revoked": false
}
]
}DELETE /v1/api_keys/{id}
Revokes an API key. Future requests with this key return 401 immediately. Cannot be undone — issue a new key with POST /v1/api_keys if needed.
curl -X DELETE https://api.settle.xxx/v1/api_keys/key_01HZX... \
-H "Authorization: Bearer sk_live_..."{ "ok": true, "revoked": "key_01HZX..." }