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
| Field | Type | Required | Description |
|---|---|---|---|
amount_usd | number | yes* | Amount in USD. Required unless amount_original + currency are supplied. |
amount_original | number | no | Amount in the original currency. Used with currency. |
currency | string | no | ISO-4217 code (EUR, GBP, JPY, …). Defaults to USD. |
description | string | no | Human-readable description shown to the customer on the payment page. |
external_id | string | no | Your order/invoice ID. Returned on webhooks for reconciliation. |
callback_url | string | no | Where to send the customer after payment completes. |
customer_email | string | no | For receipt emails and dispute resolution. |
expires_at | ISO-8601 string | no | Override the default 30-minute expiry. Max 7 days. |
metadata | object | no | Arbitrary 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
| Status | Body | Meaning |
|---|---|---|
| 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
- Webhooks — what you get back after the customer pays.
- Payment statuses — state machine reference.