API reference

Base URL: https://app.mintagepay.com/v1 (sandbox: https://sandbox.mintagepay.com/v1). All request and response bodies are JSON. Amounts are decimal strings.

A machine-readable description of every route is at /v1/openapi.json. No key is needed to read it.

Authentication

Send your key as a bearer token:

Authorization: Bearer mp_live_YOUR_KEY

Create keys in the dashboard under Developers > API keys. A key is shown once; only its hash is stored. Revoked keys get 401.

GET /v1/me works with any key. It tells an integration where it is and what the account can do:

Scopes

Each key has scopes, chosen when it is created. A call the key's scopes do not allow gets 403 insufficient_scope, and details.required names the missing scope.

Scope Allows
invoices:read GET /v1/invoices, GET /v1/invoices/{id} (with its payments and refunds)
invoices:write POST /v1/invoices, POST /v1/invoices/{id}/send, POST /v1/invoices/{id}/cancel
links:read GET /v1/payment-links
links:write POST /v1/payment-links
orders:read GET /v1/orders/{id} (the customer's name, email and address)
refunds:read GET /v1/invoices/{id}/refunds
webhooks:manage /v1/webhook-endpoints (list, add, change, remove)

The Developers page offers presets: Full access, Read only and AI agent: read + create invoices. Only team members who may see customer details (owner, admin) can give a key orders:read.

Errors

Errors look like this:

{ "error": "not_found", "message": "Invoice not found" }

Invalid input is 400 with "error": "validation_failed" and one details item per problem:

{ "error": "validation_failed", "message": "Invalid request",
  "details": [{ "path": "redirect_url", "code": "invalid_url", "message": "Invalid URL" }] }
Status Meaning
400 Invalid input
401 Bad key
403 The key lacks the scope (insufficient_scope), or the account may not use the feature (for example review_required)
404 Not found
409 Conflict
410 Expired
429 Rate limited. The limit is 120 requests a minute per key

Create an invoice

POST /v1/invoices (scope invoices:write)

Send the header Idempotency-Key with your order id or any unique string. Repeating the same key returns the same invoice (200), so a retry never creates a second one. Reusing the key with a different amount or order id returns 409. So does reusing it with a different metadata.order_key, when both requests carry one: put a value that belongs to the one order there if your order numbers can start again (a rebuilt store, or two stores on one account).

{
  "amount": "15.00",
  "currency": "USD",
  "order_id": "1042",
  "description": "Order #1042",
  "buyer_email": "customer@example.com",
  "redirect_url": "https://example.com/thank-you",
  "cancel_url": "https://example.com/cart",
  "metadata": { "any": "json" },
  "expires_in_minutes": 60,
  "send_email": false,
  "send_receipt": true,
  "locale": "es"
}
Field Required Notes
amount Yes In currency, above zero, at most 2 decimals (none for JPY, KRW, VND, CLP, PYG, ISK, XAF and XOF, VND, CLP, PYG, ISK, XAF and XOF)
currency No Default USD. See Prices in other currencies
order_id No Your reference, up to 200 characters
description No Up to 500 characters
buyer_email No Used for the receipt and for payment request emails
redirect_url No Shown as "Return to store" after payment
cancel_url No Where the page's "Cancel" link takes a customer who has not paid yet
metadata No Any JSON, up to 4,000 characters
expires_in_minutes No 5 to 10080. Default 60
send_email No true emails the payment link to buyer_email now
send_receipt No Default true. false if your store sends its own receipt
locale No The customer's language. See Checkout language

Returns 201 with an invoice object. Send the customer to its checkout_url.

With "send_email": true the response also has "email_sent": true, or false with an email_error. The invoice is created either way, and you can retry with POST /v1/invoices/{id}/send.

Checkout language

The payment page and the customer emails come in English, Spanish, Portuguese (Brazil), French, German, Italian, Turkish, Indonesian, Vietnamese, Thai, Arabic (right to left), Hindi and Russian. locale sets the customer's language: en, es, pt-BR, fr, de, it, tr, id, vi, th, ar, hi or ru. Any language tag works (es-MX, de_DE), so a store can pass its own language as it is. A language that is not offered never fails the invoice; the page then follows the customer's browser.

On the page, the first match wins: the language the customer picks in the page's switcher, ?lang= in the URL, the language they picked on an earlier visit, the invoice's locale, the browser language, then English.

Prices in other currencies

Customers always pay in US dollar stablecoins. You can still price in your own currency. Send currency with one of: USD, EUR, GBP, CAD, AUD, CHF, JPY, MXN, BRL, INR, SGD, AED, PLN, SEK, NOK, DKK, NZD, ZAR, TRY, KRW, HKD, NGN, ARS, PHP, IDR, VND, PKR, EGP, BDT, KES, GHS, TZS, COP, CLP, PEN, UYU, PYG, BOB, DOP, GTQ, CRC, THB, MYR, TWD, LKR, NPR, KZT, UAH, ILS, SAR, QAR, CZK, HUF, RON, ISK, XAF, XOF.

Other invoice calls

Call Does
GET /v1/invoices/{id} Returns the invoice, with its payments and refunds
GET /v1/invoices?order_id=1042&status=paid&limit=20 Lists invoices, newest first, at most 100
GET /v1/invoices/{id}/refunds Returns { "data": [refund, ...] }
POST /v1/invoices/{id}/cancel Cancels an open, expired or partially_paid invoice while nothing is confirming. Otherwise 409. Money already received stays in your wallet
POST /v1/invoices/{id}/send Emails the customer the payment request. Body {"email": "customer@example.com"}, or empty to use the invoice's buyer_email. Open invoices only

Payment request emails are limited to 50 per store per hour and 10 per recipient per day (429 rate_limited).

The invoice object

{
  "id": "Kp3...",
  "object": "invoice",
  "status": "open",
  "amount": "15.00",
  "currency": "USD",
  "price_currency": "USD",
  "price_amount": "15.00",
  "fx_rate": "1",
  "fx_source": null,
  "fx_at": null,
  "amount_paid": "0.00",
  "amount_pending": "0.00",
  "amount_remaining": "15.00",
  "overpaid": "0.00",
  "amount_short": "0.00",
  "paid_late": false,
  "held_for_review": false,
  "order_id": "1042",
  "description": "Order #1042",
  "metadata": {},
  "locale": "es",
  "checkout_url": "https://pay.mintagepay.com/i/Kp3...",
  "redirect_url": "https://example.com/thank-you",
  "expires_at": "2026-10-01T19:00:00.000Z",
  "paid_at": null,
  "created_at": "2026-10-01T18:00:00.000Z",
  "payments": [
    { "network": "base", "token": "USDC", "amount": "15.000000", "status": "confirmed",
      "tx": "0x...", "explorer_url": "https://basescan.org/tx/0x...", "late": false,
      "from_address": "0x..." }
  ],
  "refunds": [],
  "amount_refunded": "0.00",
  "send_receipt": true,
  "subscription": null
}
Field Meaning
amount The USD amount due
amount_paid Confirmed on the network
amount_pending Seen on the network, not final yet
amount_remaining What is still due. "0.00" once paid
overpaid Paid above amount
amount_short Paid within your underpayment tolerance: how much less than amount arrived
paid_late true if money arrived after the invoice expired or was cancelled
held_for_review true if a payment came from a sanctioned address. Do not fulfil. See Webhooks
payments[].from_address The sending address. It may be an exchange's address, not the customer's own

Amounts inside payments and refunds have up to 6 decimals, for example "15.000000".

Status

Status Meaning
open Waiting for payment
partially_paid Some money arrived but not enough. The payment page shows the remainder
paid Confirmed payments cover amount, or fall short by no more than your underpayment tolerance. Fulfil the order
expired Time ran out before the invoice was fully paid. amount_paid is above zero if the customer paid part of it
cancelled You cancelled it

Payments are still watched for after an invoice expires or is cancelled. Money sent anyway still reaches your wallet. If it completes the amount, the invoice becomes paid with paid_late: true. If it still falls short, the status stays as it was, paid_late becomes true and invoice.partially_paid is sent.

Underpayments and overpayments

Refunds

Mintage Pay never holds your money, so you send a refund yourself, from any wallet or exchange, on the network the customer paid on. Then record it in the dashboard: open the payment under Payments, choose Refund, enter the amount and address, send it, and paste the transaction hash.

A refund counts once it is final on the network and sends the right stablecoin, at least the amount, to that address. Refunds can never add up to more than was paid. The Mintage Pay fee is not refunded. When a refund is verified, your endpoint receives invoice.refunded.

A refund object:

{ "id": "...", "network": "base", "token": "USDC", "amount": "5.000000", "to": "0x...",
  "amount_micro": "5000000", "amount_units": "5000000", "decimals": 6,
  "status": "confirmed",
  "tx": "0x...", "explorer_url": "https://basescan.org/tx/0x...", "created_at": "...", "confirmed_at": "..." }

status is requested or confirmed. Add up amount_micro (millionths of a dollar) for totals.

A payment link is a product or donation page hosted by Mintage Pay. Most stores create them in the dashboard; see Payment links and the buy button. They can also be created through the API.

POST /v1/payment-links (scope links:write) creates a link and returns it (201) with the page address in url.

Field Notes
name Required
description Optional
kind product (default) or donation
price_mode fixed (default) or open for an amount the customer chooses. Donations are open
currency Default: your default currency
unit_price The price, for fixed
open_min, open_max The lowest and highest amount, for open
shipping A shipping fee
qty_min, qty_max, stock_limit Quantity limits and stock
collect_name, collect_phone, collect_shipping What to ask the customer for. Email is always asked
custom_field_label One extra question
success_url An https page to return to after payment
presets, allow_custom Donation amounts
locale The page's language

GET /v1/payment-links (scope links:read) returns { "data": [...] }, active links first.

The answer is 403 review_required until your account has been reviewed.

Invoices created from a link carry a payment_link object: id, name, kind, order_id, quantity, currency, unit_price and shipping.

GET /v1/orders/{invoice id} (scope orders:read) returns the order with a customer object: email, name, phone, custom, message and shipping_address. Customer details are kept for 2 years, then removed.

Bank transfer

Stores approved for business-to-business sales can also offer bank transfer (ACH or wire) on the payment page, after adding a bank account in the dashboard.

Not covered here

Subscriptions and payments by AI agents (x402) can be tried in the sandbox. They are not available on live accounts yet, so they are not part of this reference.

Last updated . This page as Markdown. Something missing or wrong? Write to hello@mintagepay.com.