Create an invoice
curl -X POST https://api.stablevoy.com/v1/invoices \
-H "Authorization: Bearer svk_live_..." \
-H "Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{"amountUsdCents": 2599, "externalReference": "order-1042"}'Amounts are integer US cents. Repeating a request with the same Idempotency-Key returns the same invoice; reusing a key with different parameters is rejected with 409. The response contains one deposit address per network and the hosted checkoutUrl. Pass an optional returnUrl (your order page): after a successful payment the payment page sends the buyer back there automatically after 10 seconds, with buttons to go at once or to stay. Without it the store's default return URL from the dashboard is used; with neither, the buyer stays on the payment page. The store's website URL is a separate setting: the payment page shows it under the store name and never redirects there.
Each network in the response also has amountDueUsdCents, what the buyer is asked to send on it.
A store can have up to 1,000 invoices awaiting payment at once and create up to 60 new ones a minute; GET /v1/limits returns both. Over the first the API answers 422 open_invoice_limit_reached: cancel invoices you no longer need or let them expire. Over the second it answers 429 rate_limited with a Retry-After header. A repeated request with the same Idempotency-Key returns its invoice and never counts.
How long the buyer has to pay
Every invoice has an expiresAt. After it the payment page tells the buyer not to pay, and money that still arrives within the late window marks the invoice paid_late. Set the time per invoice with an optional expiresInMinutes, from 15 minutes to 10080 (7 days). Without it the store's payment window from the dashboard Settings tab is used, and without that the platform default; GET /v1/limits returns the default and the bounds. A value outside the bounds is refused with 422 invalid_invoice_lifetime, never shortened. Keep it short for a shop checkout, where stock is held for the order, and use a day or more for an invoice you send by email.
On Tron the last digits of the amount identify the invoice, and they stay reserved for the invoice lifetime plus the late window. A long lifetime therefore keeps them taken longer; your wallet has 9,999 of them, so this only matters at very high volume.
Tron: direct payments
Tron works differently from the EVM networks. There is no deposit contract and no forwarding: the buyer pays your own Tron wallet directly, and Stablevoy takes no fee on Tron. In the invoice response and on the payment page the Tron entry has settlement: "direct", your wallet as depositAddress andpayoutAddress, no contract addresses, and amountDueMicros, the exact amount to send.
Each Tron invoice asks for its amount plus a few thousandths of a cent, for example 25.990137 instead of 25.99. Those last four digits identify the order. If an exchange takes its withdrawal fee out of the amount, the digits still match and the payment is attached to the order, which is then marked underpaid or overpaid by the usual rules. A transfer that matches no order is listed in your dashboard, and you assign it to the right order by hand.
Your Tron payout address is set in the dashboard like the EVM one. It can be filled in from TronLink in the browser or from a mobile wallet over WalletConnect, and the change is still confirmed with a signature of your login wallet. On the payment page the buyer can pay with TronLink or from a mobile wallet over WalletConnect. For the mobile wallet the API builds the transfer through its own Tron node with POST /v1/checkout/invoices/{invoiceId}/tron/transfer and sends the signed one with POST /v1/checkout/invoices/{invoiceId}/tron/broadcast. The page decodes the transaction itself before the wallet is asked to sign it, and the API refuses anything that is not the exact payment of that invoice.
GET /v1/unmatched-transfers lists the unmatched transfers, and in the dashboard POST /v1/unmatched-transfers/{transferId}/assign with {"invoiceId": "..."} attaches one to an invoice of yours that was offered on Tron, up to seven days after it expired. The usual statuses and webhooks follow from the amount that arrived. API keys cannot assign transfers.
A store API key may read the list too, for the networks that store offers, at most 30 times a minute. The payout wallet belongs to your account rather than to one store, and an unmatched transfer matched no invoice, so each item has sharedWithStores: 1 means it is certainly this store's; a larger number means it may belong to any of that many of your stores.
Invoice statuses
awaiting_payment,confirming: open.paid,overpaid,paid_late: settled, ship the order.underpaid,expired: not enough was received in time.on_hold: the payer address is on the OFAC sanctions list. Funds still only go to your wallet; decide with your compliance policy.cancelled: you cancelled it before any payment was seen. Money that still arrives later makes itpaid_late.
Totals for a period
curl "https://api.stablevoy.com/v1/invoices/summary?period=last_7_days" \
-H "Authorization: Bearer svk_live_..."GET /v1/invoices/summary adds up the invoices of the key's store created in a range of UTC days: how many are in each status, invoicedUsdCents (cancelled and expired invoices left out) and receivedUsdMicros, the money that counts for them, also per network and token in byNetwork. Pass either period (last_7_days, last_30_days, this_month, last_month or last_3_months) or both createdFrom and createdTo as YYYY-MM-DD. One request covers at most 92 days, so ask for a year in four parts; anything else is refused with 400 invalid_invoice_filter. A store can ask 30 times a minute.
Cancelling an invoice
When an order is cancelled in your shop, cancel its invoice with POST /v1/invoices/{invoiceId}/cancel (your store's API key), or with the Cancel button in the dashboard invoice list. The payment page then tells the buyer not to pay, and the invoice.cancelled webhook is sent. Cancelling twice returns the invoice unchanged.
Only an invoice still waiting for its first payment can be cancelled. Once a transfer has been seen on chain the API answers 409 invoice_not_cancellable, and 409 conflict if a payment arrives at the very moment of the cancel; read the invoice and handle it as it is. Payments are non-custodial, so a buyer who pays after the cancel anyway still pays your wallet: the invoice becomes paid_late with the usual webhook, and you refund or fulfil the order.