Merchant API
Use the merchant API to create payments and invoices, read their status and send payouts.
Your shop's server calls the merchant API to create payments and invoices and to read their status. The API keeps the paths, fields, statuses and IPN signature of the common hosted-gateway API. The API reference lists every field and schema.
Base URL
The base URL is your site URL (TURMITE_SITE_URL). Every path starts with /v1/.
| Site URL | Example request |
|---|---|
https://pay.example.com |
https://pay.example.com/v1/payment |
https://example.com/pay |
https://example.com/pay/v1/payment |
A project's checkout domain also serves the merchant API. A shop can use its own checkout domain as the base URL. See Projects and checkout domains.
Authentication
Send the project API key in the x-api-key header. Keys start with tm_. Each key belongs to one project, and the API only shows data of that project.
- Open the dashboard and select the project.
- Open API keys.
- Under Create a key, enter a label and a fresh two-factor code. Then click Create key.
- Copy the key. The dashboard shows it one time.
You need the project_admin role to create a key. A developer can revoke a key. Turmite stores only a SHA-256 hash of each key.
Keep the key on your server. Do not put it in browser code or in a mobile app.
curl https://pay.example.com/v1/status
curl -H "x-api-key: $TM_API_KEY" https://pay.example.com/v1/merchant/coins
The payout endpoints also need a short-lived token from POST /v1/auth. See Payouts.
Requests and responses
- Send JSON bodies with
Content-Type: application/json. Turmite ignores fields that it does not know. - Tickers (
pay_currency,currency) are lowercase, for examplebtcorusdttrc20. See Tickers and networks. - Request amounts can be a JSON number or a decimal string, for example
25or"25.00". POST /v1/payment,POST /v1/invoice,POST /v1/invoice-paymentandPOST /v1/payoutanswer201with the new object. A retry that returns an existing object also gets201.- Payment amounts in responses are JSON numbers. A whole amount is an integer:
25, not25.0. An amount below 0.00001 can be in exponent form, for example1e-6. Invoice amounts, payout amounts andestimated_amountare decimal strings. - Timestamps are ISO 8601 in UTC, with milliseconds.
- Each client IP address can send 600 requests per minute. Above that, the API returns
429. - An error has this shape. See Errors.
{"status": false, "statusCode": 400, "code": "INVALID_REQUEST_PARAMS", "message": "Currency `btc` is not enabled for this project. Add a Bitcoin wallet in the dashboard."}
Price currencies and rates
price_currency is a currency code that the rate source knows, for example usd, eur or btc. Responses write it in lowercase.
Turmite gets rates from CoinGecko. When CoinGecko fails, it uses Coinbase. It keeps each rate for 60 seconds. USDT and USDC count as 1 USD. When price_currency is the ticker or the symbol of pay_currency, for example usdt for usdttrc20, Turmite does not convert the price.
When no source gives a rate, the request gets 503 with the code UNAVAILABLE. Try again after a minute.
Endpoints
| Method | Path | Auth | Use |
|---|---|---|---|
| GET | /v1/status |
none | Health of the API |
| GET | /v1/currencies |
key optional | Enabled tickers |
| GET | /v1/full-currencies |
key optional | Details per ticker |
| GET | /v1/merchant/coins |
key | Tickers enabled for the project |
| GET | /v1/min-amount |
none | Smallest payment for a ticker |
| GET | /v1/estimate |
none | Price conversion |
| POST | /v1/payment |
key | Create a payment |
| GET | /v1/payment/{payment_id} |
key | Read one payment |
| GET | /v1/payment |
key | List payments |
| POST | /v1/invoice |
key | Create an invoice with a checkout link |
| POST | /v1/invoice-payment |
key | Create a payment for an invoice |
| GET | /v1/balance |
key | Credited and pending amounts per ticker |
| POST | /v1/auth |
none | Get a token for payouts |
| POST | /v1/payout |
key and token | Create a payout batch |
| POST | /v1/payout/{batch_id}/verify |
key and token | Confirm a batch with a two-factor code |
| GET | /v1/payout/{batch_id} |
key | Read one batch |
| GET | /v1/payout |
key and token | List payouts |
| POST | /v1/payout/validate-address |
key | Check a destination address |
| GET | /v1/payout/fee |
key | Estimate the network fee |
"Key optional" endpoints answer for the key's project when you send a key, and for the whole install when you do not. A wrong key gets 401.
Status and currencies
GET /v1/status returns {"message":"OK"} when the API is up. For load balancer checks, use /healthz. It returns ok.
GET /v1/currencies returns the tickers that the key's project accepts. A project accepts a ticker when the ticker and its network are enabled and the project has a wallet on that network. Without a key, the list has the tickers that at least one project accepts.
curl -H "x-api-key: $TM_API_KEY" https://pay.example.com/v1/currencies
{"currencies": ["btc", "xmr", "eth", "usdc", "usdterc20", "usdcbase", "usdttrc20", "sol"]}
GET /v1/full-currencies returns every enabled ticker of the install, with details.
{
"currencies": [
{
"id": 7,
"code": "USDTTRC20",
"name": "Tether USD",
"enable": true,
"wallet_regex": null,
"priority": 7,
"extra_id_exists": false,
"extra_id_regex": null,
"logo_url": null,
"track": false,
"cg_id": "tether",
"is_maxlimit": false,
"network": "tron",
"smart_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"network_precision": "6",
"ticker": "usdttrc20"
}
]
}
ticker is the value to send as pay_currency. code is the ticker in upper case. enable is true when the project accepts the ticker. network_precision is the number of decimals, as a string. smart_contract is null for a native coin. wallet_regex, extra_id_regex and logo_url are always null.
GET /v1/merchant/coins returns the tickers that the project accepts.
{"selectedCurrencies": ["btc", "usdcbase", "usdttrc20"]}
Estimates
GET /v1/min-amount returns the smallest payment for a ticker.
| Parameter | Required | Description |
|---|---|---|
currency_from |
yes | The ticker, for example btc |
currency_to |
no | Copied to the response. The default is currency_from |
fiat_equivalent |
no | A fiat code. The response then has fiat_equivalent, the minimum in that currency, when a rate is available |
curl "https://pay.example.com/v1/min-amount?currency_from=btc&fiat_equivalent=usd"
{"currency_from": "btc", "currency_to": "btc", "min_amount": 1e-6, "fiat_equivalent": 0.07}
When the operator set no minimum for the ticker, min_amount is a default that depends on the decimals of the ticker. POST /v1/payment refuses an amount below min_amount with 400 and the code INVALID_REQUEST_PARAMS.
GET /v1/estimate converts a price into a ticker amount at the current rate. Turmite rounds the amount up to the decimals of the ticker.
| Parameter | Required | Description |
|---|---|---|
amount |
yes | The price |
currency_from |
yes | The price currency, for example usd |
currency_to |
yes | The ticker, for example btc |
curl "https://pay.example.com/v1/estimate?amount=25¤cy_from=usd¤cy_to=btc"
{"currency_from": "usd", "amount_from": 25, "currency_to": "btc", "estimated_amount": "0.00041235"}
Payments or invoices
Turmite has two ways to take a payment.
| Use | Create | The customer sees |
|---|---|---|
| Your shop shows the address and the amount on its own page | POST /v1/payment |
Your page |
| Turmite shows the checkout | POST /v1/invoice |
The invoice page at invoice_url, then the payment page |
The checkout pages work with JavaScript off. See Checkout.
Create a payment
POST /v1/payment creates a payment with its own deposit address.
| Field | Required | Description |
|---|---|---|
price_amount |
yes | Price in price_currency. It must be more than 0 |
price_currency |
yes | Currency of the price, for example usd |
pay_currency |
yes | Ticker the customer pays with, for example btc. The project must accept it |
pay_amount |
no | A fixed amount in pay_currency. Without it, Turmite converts price_amount at the current rate |
order_id |
no | Your order number, up to 255 characters. Webhooks include it |
order_description |
no | Text for your records, up to 1000 characters |
ipn_callback_url |
no | URL that receives a webhook at each status change |
success_url |
no | Page for the "Return to store" button after payment |
cancel_url |
no | Page for the return button after the payment expires |
purchase_id |
no | Your purchase reference, up to 255 characters. Without it, Turmite makes a 10-digit one |
idempotency_key |
no | Up to 255 characters. A retry with the same key returns the first payment. You can send it in the Idempotency-Key header instead |
The three URLs must be https URLs without a user name or password. A testnet install also accepts http. If the project has Allowed return hosts under Settings → Checkout, success_url and cancel_url must use one of these hosts.
Send idempotency_key when your shop can retry the request, for example after a timeout. Use one key per order. A request with a known key returns the first payment, even when other fields are different.
curl -X POST https://pay.example.com/v1/payment \
-H "x-api-key: $TM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"price_amount": 25,
"price_currency": "usd",
"pay_currency": "btc",
"order_id": "ORDER-1001",
"order_description": "Order 1001",
"ipn_callback_url": "https://shop.example.com/turmite/ipn",
"idempotency_key": "ORDER-1001"
}'
The response is 201 with a payment object in the status waiting. Show pay_address and pay_amount to the customer on your own page, or send the customer to payment_url, the hosted payment page. Save payment_id with the order.
{
"payment_id": "5524759814",
"invoice_id": null,
"payment_status": "waiting",
"pay_address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"payin_extra_id": null,
"price_amount": 25,
"price_currency": "usd",
"pay_amount": 0.00041235,
"actually_paid": 0,
"pay_currency": "btc",
"order_id": "ORDER-1001",
"order_description": "Order 1001",
"purchase_id": "4709931082",
"outcome_amount": null,
"outcome_currency": "btc",
"network": "btc",
"network_precision": 8,
"smart_contract": null,
"payin_hash": null,
"payout_hash": null,
"payment_url": "https://pay.example.com/pay/Q7mX2pK9vT4nB8cR1sW6yZ3h",
"created_at": "2026-10-23T12:00:00.000Z",
"updated_at": "2026-10-23T12:00:00.000Z",
"expiration_estimate_date": "2026-10-23T13:00:00.000Z",
"parent_payment_id": null,
"type": "crypto2crypto",
"time_limit": null,
"burning_percent": null,
"payment_extra_ids": null,
"amount_received": 0.00041235,
"actually_paid_at_fiat": 0,
"fee": {"currency": "btc", "depositFee": 0, "withdrawalFee": 0, "serviceFee": 0}
}
In this response, payment_id and purchase_id are strings. See The payment object for the types in the other responses.
A payment expires after the project's payment window, 60 minutes by default. Change it in Settings under Payment window (minutes), from 5 to 10080.
When the network has no free deposit address, or no node is connected for it, the request gets 503 with Retry-After: 30. Send it again after 30 seconds.
Create an invoice
POST /v1/invoice creates an invoice and returns invoice_url. Send the customer to that URL. The customer selects a coin, and Turmite creates the payment.
| Field | Required | Description |
|---|---|---|
price_amount |
yes | Price in price_currency |
price_currency |
yes | Currency of the price |
pay_currency |
no | Ticker to use. Set it to skip the coin selection on the invoice page |
order_id |
no | Your order number |
order_description |
no | Text for your records |
ipn_callback_url |
no | URL that receives the webhooks of the invoice's payments |
success_url |
no | Page for the "Return to store" button |
cancel_url |
no | Page for the return button after the payment expires |
The limits and URL rules are the same as for POST /v1/payment. The payment that the customer starts copies order_id, order_description, ipn_callback_url, success_url and cancel_url from the invoice.
curl -X POST https://pay.example.com/v1/invoice \
-H "x-api-key: $TM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"price_amount": 25,
"price_currency": "usd",
"order_id": "ORDER-1001",
"order_description": "Order 1001",
"ipn_callback_url": "https://shop.example.com/turmite/ipn",
"success_url": "https://shop.example.com/orders/1001",
"cancel_url": "https://shop.example.com/cart"
}'
{
"id": "4522625843",
"token_id": "k3J9xQ2vT8mZ5wR1nB7cY4pL",
"order_id": "ORDER-1001",
"order_description": "Order 1001",
"price_amount": "25",
"price_currency": "usd",
"pay_currency": null,
"ipn_callback_url": "https://shop.example.com/turmite/ipn",
"invoice_url": "https://pay.example.com/i/k3J9xQ2vT8mZ5wR1nB7cY4pL",
"success_url": "https://shop.example.com/orders/1001",
"cancel_url": "https://shop.example.com/cart",
"created_at": "2026-10-23T12:00:00.000Z",
"updated_at": "2026-10-23T12:00:00.000Z",
"expiration_estimate_date": "2026-10-24T12:00:00.000Z"
}
id is numeric text. price_amount is a decimal string. An invoice does not change, so updated_at is always created_at. An invoice stays open for the project's invoice window, 1440 minutes (24 hours) by default. Change it in Settings under Invoice window (minutes), from 5 to 43200.
If the project has a checkout domain, invoice_url uses it.
Create a payment for an invoice
POST /v1/invoice-payment creates the payment for an invoice from your own coin picker. Send the invoice id as iid and the ticker as pay_currency. purchase_id is optional. The response is a payment object with invoice_id set. The payment gets the price, the order and the URLs of the invoice.
If the invoice already has an open payment in that ticker, the response is that payment. An expired invoice gets 400.
curl -X POST https://pay.example.com/v1/invoice-payment \
-H "x-api-key: $TM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"iid": "4522625843", "pay_currency": "usdttrc20"}'
Read payments
GET /v1/payment/{payment_id} returns one payment. A payment of another project gets 404.
curl -H "x-api-key: $TM_API_KEY" https://pay.example.com/v1/payment/5524759814
GET /v1/payment returns a page of payments.
| Parameter | Description |
|---|---|
limit |
Payments per page, from 1 to 500. The default is 10 |
page |
Page number. The first page is 0 |
sortBy |
created_at (default), updated_at, payment_id, payment_status, pay_address, price_amount, price_currency, pay_amount, actually_paid, pay_currency, outcome_amount, outcome_currency, order_id, order_description or purchase_id |
orderBy |
asc or desc. The default is desc |
dateFrom, dateTo |
Range of created_at. A date (2026-10-01) or a date-time (2026-10-31T23:59:59Z). A date in dateTo includes the full day |
payment_status |
Only payments with this status |
order_id |
Only payments with this order ID |
invoiceId |
Only payments of this invoice |
curl -H "x-api-key: $TM_API_KEY" \
"https://pay.example.com/v1/payment?limit=10&page=0&sortBy=created_at&orderBy=desc"
{"data": [{"payment_id": 5524759814, "payment_status": "finished"}], "limit": 10, "page": 0, "pagesCount": 1, "total": 1}
The example shows two fields of each payment. Each item in data is a full payment object.
Use webhooks to learn about status changes. Use these endpoints to check a status again or to reconcile.
Balance
GET /v1/balance returns two amounts for each ticker that has payments in the project.
| Field | Value |
|---|---|
amount |
The sum of outcome_amount of the project's payments, refunded payments excluded |
pendingAmount |
The amount still due on open payments (waiting, confirming, partially_paid, confirmed): pay_amount minus the credited amount |
{"btc": {"amount": 0.0153, "pendingAmount": 0.0012}, "usdttrc20": {"amount": 1250.5, "pendingAmount": 0}}
The balance is a total of payments. It is not the balance of a wallet. Payouts do not decrease it. A payment that you mark as refunded leaves it.
Payouts
Payouts send funds from the project's hot wallet. Read Payouts for the setup, the limits and the approval rules.
POST /v1/authwith the email address and password of a dashboard user returns a token. The token is valid for 5 minutes.POST /v1/payoutwith the API key and the token creates a batch. The user must be a project admin. Each address must be in the project's address book. New payouts have the statuspending-otp-verification.POST /v1/payout/{batch_id}/verifywith the user's current two-factor code releases the batch. The project limits then set each payout topendingorpending-approval.
The full example is in Create a payout with the API. POST /v1/auth accepts 10 attempts per minute for each IP address and for each email address.
The payment object
| Field | Description |
|---|---|
payment_id |
ID of the payment |
invoice_id |
ID of the invoice as a number, or null |
payment_status |
Status. See Payment statuses |
pay_address |
Deposit address for this payment |
payin_extra_id |
Always null. No supported network needs a deposit memo |
price_amount |
Price in price_currency |
price_currency |
Currency of the price |
pay_amount |
Amount the customer must send, in pay_currency |
actually_paid |
Amount of all deposits seen so far, confirmed or not |
pay_currency |
Ticker the customer pays with |
order_id |
Your order number |
order_description |
Your description |
purchase_id |
Your purchase reference, or a 10-digit number that Turmite makes |
outcome_amount |
Confirmed amount credited to the project, or null before the first credit. Sweep fees are not subtracted |
outcome_currency |
Same as pay_currency |
network |
Network code: btc, xmr, eth, base, polygon, bsc, tron, sol or ton |
network_precision |
Decimals of pay_currency |
smart_contract |
Token contract, or null for a native coin |
payin_hash |
Hash of the latest deposit transaction, or null |
payout_hash |
Hash of the latest sweep from the deposit address to your wallet, or null. Always null on Bitcoin and Monero |
payment_url |
Hosted payment page, <site URL or checkout domain>/pay/<token> |
created_at |
Time of creation |
updated_at |
Time of the last change |
expiration_estimate_date |
End of the payment window |
amount_received |
Amount the project gets in outcome_currency: outcome_amount after the first credit, pay_amount before it |
actually_paid_at_fiat |
actually_paid in price_currency at the rate of the payment. For a fixed pay_amount, the rate is price_amount / pay_amount |
fee |
{currency, depositFee, withdrawalFee, serviceFee}. Turmite takes no fee: depositFee and serviceFee are 0. withdrawalFee is the network fee of the sweeps to your wallet |
type |
Always crypto2crypto |
parent_payment_id, time_limit, burning_percent, payment_extra_ids |
Always null |
The types of two IDs depend on the response. Typed clients of the common hosted-gateway API expect these types.
| Response | payment_id |
purchase_id |
|---|---|---|
POST /v1/payment, POST /v1/invoice-payment |
String | String |
GET /v1/payment/{payment_id}, GET /v1/payment |
Number | Number when it is a plain integer up to 2^53 - 1, else string |
| Webhooks | Number | String |
The payment object keeps the field names of the common hosted-gateway API. network_precision, smart_contract, payin_hash, payout_hash and payment_url are extra fields.
The checkout page uses a random token in its URL. The numeric payment_id never appears in a checkout link.
Statuses in short
Ship the order when the status is finished. The full list and the order of statuses are in Payment statuses. Webhooks tell you about each change. See Webhooks.