Documentation

Monetasync API reference

Everything documented here is genuinely implemented and callable now. Anything not yet built is stated explicitly.

QuickstartRepresenting moneyAuthenticationIdempotencyEndpointsWebhooksSDKMCPErrors

Quickstart

Create an account, copy your test key, then create your first payment.

curl
curl -X POST https://monetasync.com/v1/payments \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1001" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "100.00",
    "display_currency": "USD",
    "order_id": "order-1001",
    "fee_payer": "customer",
    "success_url": "https://your-site.com/thanks"
  }'

The response contains checkout_url. Send your customer there.

Representing money

Every monetary field is a string holding an integer count of the smallest unit. Ten ETH is 10000000000000000000 wei, which exceeds what a JavaScript number can represent exactly. Use BigInt or a decimal library — never Number().
javascript
// Wrong: silently loses precision
const wei = Number(payment.required_amount);   // 10000000000000000000 -> 1e19

// Right
const wei = BigInt(payment.required_amount);   // exact

Authentication

Send the key in the Authorization header. Keys begin with sk_test_ or sk_live_ and are stored hashed, so the secret value is shown exactly once at creation.

header
Authorization: Bearer sk_test_...

Idempotency

Send an Idempotency-Key header on every create request. Replaying the same request returns the original response with idempotent-replay: true and does not create a second payment. Your order id makes a good key.

Endpoints

MethodPathDescriptionKey
POST /v1/payments Create a payment, returns a checkout URL Required
GET /v1/payments List recent payments Required
GET /v1/payments/:id Retrieve one payment Required
GET /v1/balances Merchant balances derived from the ledger Required
GET /v1/networks Per-network operational state Public
GET /v1/assets Asset and network pairs Public
POST /v1/payment-links Create a shareable payment link Required
GET /v1/payment-links List payment links Required
DELETE /v1/payment-links/:id Archive a payment link Required
POST /v1/payouts Request a payout to an allowlisted address Required
GET /v1/payouts List payouts Required
GET /v1/payouts/:id Retrieve one payout Required
POST /v1/payout-addresses Add a payout address (starts in cooldown) Required
GET /v1/payout-addresses List payout addresses Required
DELETE /v1/payout-addresses/:id Remove a payout address Required
POST /v1/webhook-endpoints Register a webhook endpoint; the secret is returned once Required
GET /v1/webhook-endpoints List webhook endpoints (never the secrets) Required
DELETE /v1/webhook-endpoints/:id Delete a webhook endpoint Required
GET /v1/events Webhook delivery log Required
GET /v1/verification Verification status and whether payouts are open Required
Identity documents are uploaded from the dashboard only, never over the API — ID images must not travel through an API key stored on a merchant's server. Payout amounts are sent as integer strings of base units (amount_base), so no rounding can occur.

Webhooks

Every message is signed with HMAC-SHA256. The timestamp is part of the signed material, which is what prevents an old request being replayed.

header
X-Payment-Signature: t=1700000000,v1=<hex hmac-sha256>
signed payload = "<t>.<raw body>"
webhook handler
import { verifyWebhook } from "@settlekit/sdk";

export async function POST(request) {
  // Use the RAW body. Re-serialising the JSON changes the bytes
  // and the signature will no longer match.
  const raw = await request.text();
  const event = await verifyWebhook(
    raw,
    request.headers.get(WEBHOOK_SIGNATURE_HEADER),
    process.env.FAWTERLI_WEBHOOK_SECRET
  );

  if (event.type === "payment.paid") {
    await fulfilOrder(event.data.orderId);
  }
  return new Response(null, { status: 204 });
}

Five attempts over roughly two hours with exponential backoff. Return 2xx quickly and do slow work afterwards.

SDK

@settlekit/sdk
import { Fawterli } from "@settlekit/sdk";

const settlekit = new SettleKit({
  apiKey: process.env.PAYMENTS_SECRET_KEY,
  baseUrl: "https://monetasync.com"
});

const payment = await settlekit.payments.create({
  amount: "100.00",          // string, always
  displayCurrency: "USD",
  orderId: "course-1001",    // doubles as the idempotency key
  feePayer: "customer",
  successUrl: "https://merchant.example/success"
});

redirect(payment.checkout_url);

Runs on Node 18+, Cloudflare Workers, Deno, Bun and the browser. Passing a number instead of a string for amount raises a type error.

MCP server

Lets AI coding tools create payments and read balances safely. Payout tools are off by default and only appear when payouts:write is granted explicitly.

mcp config
{
  "mcpServers": {
    "fawterli": {
      "command": "npx",
      "args": ["-y", "@settlekit/mcp-server"],
      "env": {
        "PAYMENTS_SECRET_KEY": "sk_test_...",
        "PAYMENTS_BASE_URL": "https://monetasync.com",
        "FAWTERLI_SCOPES": "payments:read,payments:write,balances:read"
      }
    }
  }
}

The key is passed via environment variables rather than the command line, so it does not appear in the process table. No tool exposes private keys, and no tool signs a transaction directly.

Errors

CodeHTTPMeaning
unauthorized 401 Missing, revoked or unknown API key
invalid_amount 400 Amount is not a valid decimal string like "100.00"
invalid_json 400 Body was not valid JSON
not_found 404 Not found, or belongs to another merchant
internal_error 500 Unexpected failure

Every response carries an x-request-id header. Quote it when reporting a problem.

Get an API key Try /v1/networks OpenAPI