Skip to main content
The merchant API gives you programmatic payments with three endpoints and zero payment UI: create an intent, redirect the buyer to the returned checkoutUrl, and react to the webhook. Amounts are per-order, titles are per-order — everything a shop needs. You’ll need an API key with the Write scope (plus Read for fetching).

Step 1 — Create a payment intent

curl -s -X POST https://qint-api.fly.dev/api/v1/intents \
  -H "Authorization: Bearer $QINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 49.90,
    "currency": "CHF",
    "title": "Order #1001",
    "idempotencyKey": "order-1001",
    "returnUrl": "https://shop.example.ch/checkout/order-received/1001"
  }'
const res = await fetch("https://qint-api.fly.dev/api/v1/intents", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.QINT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 49.9,
    currency: "CHF",
    title: "Order #1001",
    idempotencyKey: "order-1001",
    returnUrl: "https://shop.example.ch/checkout/order-received/1001",
  }),
});
const intent = await res.json(); // res.status: 201 new, 200 idempotent replay
$ch = curl_init('https://qint-api.fly.dev/api/v1/intents');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('QINT_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 49.90,
        'currency' => 'CHF',
        'title' => 'Order #1001',
        'idempotencyKey' => 'order-1001',
        'returnUrl' => 'https://shop.example.ch/checkout/order-received/1001',
    ]),
]);
$intent = json_decode(curl_exec($ch), true);
import os, requests

res = requests.post(
    "https://qint-api.fly.dev/api/v1/intents",
    headers={"Authorization": f"Bearer {os.environ['QINT_API_KEY']}"},
    json={
        "amount": 49.90,
        "currency": "CHF",
        "title": "Order #1001",
        "idempotencyKey": "order-1001",
        "returnUrl": "https://shop.example.ch/checkout/order-received/1001",
    },
)
intent = res.json()  # res.status_code: 201 new, 200 idempotent replay
Response (201 Created)
{
  "id": "pi_x7k2m9p4q1w8",
  "status": "initiated",
  "amount": 49.90,
  "currency": "CHF",
  "title": "Order #1001",
  "assetSymbol": null,
  "cryptoAmount": null,
  "depositAddress": null,
  "checkoutUrl": "https://checkout.qint.ch/pay/pi_x7k2m9p4q1w8",
  "returnUrl": "https://shop.example.ch/checkout/order-received/1001",
  "createdAt": "2026-07-08T09:15:00+00:00",
  "expiresAt": "2026-07-08T09:30:00+00:00",
  "confirmedAt": null,
  "settledAt": null
}
Field rules (full reference):
FieldRequiredNotes
amountyesPositive number, in the fiat currency.
currencyyesCHF, EUR or USD — nothing else.
titlenoShown on the checkout; falls back to your merchant display name.
idempotencyKeyno, but recommendedUnique per merchant — see idempotency.
returnUrlnoAbsolute https URL, max 500 chars — see return URL.

Step 2 — Redirect the buyer

Send the buyer to checkoutUrl. The hosted checkout shows your title, amount and branding, lets the buyer pick an asset and network, and displays the exact crypto amount and deposit address. The whole attempt lives inside the 15-minute window that started at creation (expiresAt).

Step 3 — Learn the outcome

Webhooks are the source of truth. Create a webhook endpoint and handle payment.status events — you’ll get one for every status transition. Fulfil the order when the status reaches settled. Reconcile with GET. Whether as a fallback for a missed webhook, on the buyer’s return to your site, or in a periodic sweep:
curl -s https://qint-api.fly.dev/api/v1/intents/pi_x7k2m9p4q1w8 \
  -H "Authorization: Bearer $QINT_API_KEY"
The reported status is live — an intent whose window lapsed reads expired immediately. You can also list intents with a status filter and paging.

The status lifecycle

initiated ──► pending ──► confirmed ──► settled
    │            │            │
    └────────────┴────────────┴──► failed / expired / cancelled
StatusMeaning
initiatedCreated; buyer hasn’t paid yet.
pendingDeposit observed, awaiting confirmation.
confirmedConfirmed on-chain, settlement in progress.
settledDone — fulfil on this status, not before.
failedThe payment failed.
expiredThe 15-minute window passed without a completed payment.
cancelledThe payment was cancelled.
Terminal states never change — with one deliberate exception: you can accept an underpaid, expired payment from the dashboard review queue, which moves it expired → settled with all settlement side effects.

Idempotency

idempotencyKey is unique per merchant. If a create request is retried with a key you’ve used before, the API returns the original intent with 200 OK instead of minting a duplicate (a fresh create returns 201 Created).
Always send an idempotency key derived from your own records — your order id is perfect. Then a network timeout is never scary: retry the exact same request and you’ll get the same intent back.

The return URL

Pass returnUrl (absolute https, ≤ 500 characters) and the hosted checkout shows a “Return to merchant” action. When the buyer clicks it, they navigate back to your URL with two query parameters appended:
https://shop.example.ch/checkout/order-received/1001?qint_intent=pi_x7k2m9p4q1w8&status=settled
ParameterValue
qint_intentThe payment intent id (pi_…).
statusThe intent’s status at the moment of return.
The return redirect is a UX convenience, not a payment confirmation — the buyer may return before settlement, or never return at all. Verify with the webhook or GET /api/v1/intents/{id} before fulfilling.