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 keyssk_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 keyspk_live_…, pk_test_…. Client-safe. Origin-bound to your verified domain. Limited to POST /v1/invoices and GET /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.

StatusMeaning
400Malformed request, missing required field.
401Missing, invalid, or revoked API key.
403Key doesn’t have permission for this endpoint (e.g. simulate_payment with a live key).
404Resource doesn’t exist or isn’t visible to you.
409Conflict — e.g. voiding an already-paid invoice, confirming a payout address that wasn’t registered.
422Policy denial — OFAC block, hard cap exceeded, signature didn’t cover the challenge.
429Rate limited. Back off and retry — see rate limits.
5xxOur 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.

ParamTypeRequiredDescription
frameworkenumyesOne 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.

ParamTypeRequiredDescription
chainenumyesbase.
addressstringyesThe 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.

ParamTypeRequiredDescription
addressstringyesMust match the pending registration for this chain.
signaturestringyesSignature 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.

ParamTypeRequiredDescription
domainstringyesThe 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.

ParamTypeRequiredDescription
min_invoice_usdnumbernoDefault 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.

ParamTypeRequiredDescription
logo_urlstring (url)noHTTPS URL to a square SVG or PNG.
accent_hexstringno6-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).

ParamTypeRequiredDescription
amount_usdnumberyesPositive, max 500000 per transaction.
accepted_currenciesstring[]noFilter, e.g. ["usdc-base", "usdc"]. Defaults to all enabled.
customer_emailstringnoUsed for receipts only. Never shared on-chain.
descriptionstringnoUp to 500 chars. Shown on the checkout page.
metadataobjectnoUp to 50 string-only keys. Echoed on every webhook.
return_urlstring (url)noBuyer redirected here after pay.
cancel_urlstring (url)noBuyer redirected here on cancel.
expires_innumber (sec)noDefault 3600, max 604800 (7d).
idempotency_keystringnoBody-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 — pendingpaid | 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 paramTypeDescription
statusenumpending, paid, expired, voided.
afterISO8601Created at or after this timestamp.
beforeISO8601Created at or before this timestamp.
customer_emailstringExact match.
metadata_keystringReturns 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 is sk_live_. 409 if the invoice is not pending.

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.

ParamTypeRequiredDescription
urlstring (https url)yesPlain HTTPS only — http:// is rejected.
eventsstring[]yesOne 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.

ParamTypeRequiredDescription
urlstring (https url)noReplace the destination URL.
eventsstring[]noReplace the subscribed events list.
rotate_secretbooleannoIf 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 paramTypeDescription
webhook_idstringLimit to one webhook.
statusenumdelivered, 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).

ParamTypeRequiredDescription
namestringyes1–80 chars. Shown in the dashboard for identification.
modeenumyestest 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..." }
Was this helpful?