Create a Payment

Full reference for POST /api/payments — every field, every response, and copy-paste examples in cURL, Node.js, and Python.

POST https://mutopay.com/api/payments creates a payment link. Authenticate with a channel API key in the X-API-Key header.

Request

POST /api/payments HTTP/1.1
Host: mutopay.com
X-API-Key: ep_live_4b8f...
Content-Type: application/json

Body

FieldTypeRequiredDescription
amount_usdnumberyes*Amount in USD. Required unless amount_original + currency are supplied.
amount_originalnumbernoAmount in the original currency. Used with currency.
currencystringnoISO-4217 code (EUR, GBP, JPY, …). Defaults to USD.
descriptionstringnoHuman-readable description shown to the customer on the payment page.
external_idstringnoYour order/invoice ID. Returned on webhooks for reconciliation.
callback_urlstringnoWhere to send the customer after payment completes.
customer_emailstringnoFor receipt emails and dispute resolution.
expires_atISO-8601 stringnoOverride the default 30-minute expiry. Max 7 days.
metadataobjectnoArbitrary JSON metadata, echoed back on webhooks. ≤ 4 KB.

Response — 201 Created

{
  "id": "pay_abc123",
  "status": "pending",
  "url": "https://mutopay.com/pay/pay_abc123",
  "amount_usd": 25.00,
  "currency": "USD",
  "description": "Order #1042",
  "external_id": "order_1042",
  "expires_at": "2026-04-21T14:30:00Z",
  "created_at": "2026-04-21T14:00:00Z"
}

Redirect your customer to url.

Examples

cURL

curl -X POST https://mutopay.com/api/payments \
  -H "X-API-Key: $MUTOPAY_CHANNEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_usd": 25.00,
    "description": "Order #1042",
    "external_id": "order_1042",
    "callback_url": "https://yourstore.com/thanks",
    "metadata": {"user_id": "u_88124", "tier": "pro"}
  }'

Node.js (fetch)

const res = await fetch('https://mutopay.com/api/payments', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.MUTOPAY_CHANNEL_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount_usd: 25.00,
    description: 'Order #1042',
    external_id: 'order_1042',
    callback_url: 'https://yourstore.com/thanks',
  }),
});

if (!res.ok) throw new Error(`MutoPay error: ${res.status}`);
const payment = await res.json();
// Redirect the customer:
//   res.redirect(payment.url);

Python (requests)

import os
import requests

res = requests.post(
    "https://mutopay.com/api/payments",
    headers={
        "X-API-Key": os.environ["MUTOPAY_CHANNEL_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "amount_usd": 25.00,
        "description": "Order #1042",
        "external_id": "order_1042",
        "callback_url": "https://yourstore.com/thanks",
    },
    timeout=10,
)
res.raise_for_status()
payment = res.json()
# Redirect the customer to payment["url"]

Non-USD pricing

Pass amount_original + currency for merchants pricing in EUR/GBP/etc. The FX rate is captured at creation time and returned in webhook payloads as fx_rate.

{
  "amount_original": 25.00,
  "currency": "EUR",
  "description": "Abonnement Pro"
}

MutoPay quotes the customer in USD equivalent, then settles in your preferred stablecoin.

Errors

StatusBodyMeaning
400{"error": "invalid_request", "message": "..."}Validation error — see message.
401{"error": "unauthorized"}Missing or invalid API key.
403{"error": "merchant suspended"}Your account is suspended. Contact support.
429{"error": "rate_limited"}Too many requests. Retry with exponential backoff.

See also