Invoices
Create, list, retrieve and void invoices; every field; quotes; and how the hosted checkout handles network selection, live status and the return to your site.
Last updated Oct 27, 2026
An invoice asks a buyer for a USD amount, paid in USDT or USDC on one network. Each invoice gets its own deposit address, so a payment is matched to its invoice by the address it was sent to.
Create an invoice
POST /api/v1/crypto/invoices — permission crypto-invoices:create, header Idempotency-Key required (see Idempotency).
There are two ways to create one:
- Fixed network and token. Send
networkandtoken. The invoice isPENDINGimmediately and the response carriesdeposit_address,totalandpayment_uri. - Buyer chooses. Omit both. The invoice takes the environment of your API key; with a legacy key, send
modeto choose it: Sandbox (mode: "test"in the API) or Live ("live"). The invoice isAWAITING_SELECTION; the buyer picks a network and token on the hosted checkout, which assigns the address then.
The smallest valid requests:
{ "amount": "49.00", "title": "Pro plan, 1 month", "network": "POLYGON_AMOY", "token": "USDC" }{ "amount": "49.00", "title": "Pro plan, 1 month", "mode": "test" }A complete request, with a seller, a partner share and a markup:
curl -X POST https://api.aliquot.cc/api/v1/crypto/invoices \
-H "Authorization: Bearer $ALIQUOT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-77310" \
-d '{
"amount": "100.00",
"network": "POLYGON_AMOY",
"token": "USDC",
"fee_payer": "CUSTOMER",
"markup_percent": 2,
"merchant": "halden_type",
"splits": [{ "account": "kerning_weekly", "percent": 10 }],
"title": "Halden Grotesk — Desktop license",
"merchant_order_id": "order-77310",
"buyer_email": "[email protected]",
"redirect_url": "https://shop.example.com/orders/77310",
"metadata": { "cart_id": "c_5521" }
}'Request fields
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Price in USD, at most two decimals. Minimum "1.00". |
title | string | Yes | What is being paid for, 1–200 characters. Shown on the checkout. |
currency | string | No | Only "USD", the default. |
network | string | With token | One of POLYGON_MAINNET, POLYGON_AMOY, BSC_MAINNET, BSC_TESTNET, BASE_MAINNET, BASE_SEPOLIA. Send it together with token, or omit both. |
token | string | With network | USDT or USDC, available on that network (see Networks and tokens). |
mode | string | With a legacy key, when no network | "test" (Sandbox) or "live". If you also send network, they must agree. A Sandbox or Live key fills in its own mode and answers 403 API_KEY_ENVIRONMENT_MISMATCH for the other one. |
fee_payer | string | No | CUSTOMER (default): the buyer pays the processing fee and the markup on top of amount. MERCHANT: the buyer pays amount and both come out of the seller's share. |
markup_percent | number | No | Your platform's cut, 0–20, at most two decimals. Default 0. |
merchant | string | No | Reference of the MERCHANT account selling. Omitted: your platform account is the seller. A PARTNER account cannot be the merchant. |
splits | array | No | Up to 20 { "account": "<reference>", "percent": <number> } entries. Each percent is above 0 with at most two decimals; together at most 100. No account twice. |
description | string | No | Up to 2000 characters. |
merchant_order_id | string | No | Your order ID, 1–200 characters. Searchable with q and included in the invoice.created and refund.claimable webhooks. |
redirect_url | string | No | Where the checkout sends the buyer after paying. See Returning to your site. |
metadata | object | No | Any JSON object. Stored and returned as is. |
buyer_email | string | No | If set, the buyer receives refund claim links by email. Never shown on the checkout. |
expires_in_minutes | integer | No | Payment window, 1–1440. Default 30. Counted from creation when you send network and token, otherwise from the moment the buyer picks a pair. See Payment window. |
How fee_payer, markup_percent and splits turn into amounts is explained, with a worked example, in Ledger and splits. Accounts named in merchant and splits must exist; an unknown reference is a 422 on that field.
The invoice object
201 Created returns the invoice. A replay of the same Idempotency-Key returns it with 200.
| Field | Description |
|---|---|
id | inv_…. |
status | See Statuses. |
livemode | false in Sandbox, true in Live. |
currency | "USD". |
fee_payer | CUSTOMER or MERCHANT. |
network, token, decimals | The payment pair and the token's decimals. null while AWAITING_SELECTION. |
base | The price in token units once a pair is selected; the USD price before. |
platform_fee, markup, merchant_net, total | The fee breakdown in token units. total is what the buyer must send. null while AWAITING_SELECTION. |
received | Sum of the transfers in the invoice's token seen so far. |
deposit_address | The address the buyer pays to. null while AWAITING_SELECTION. |
payment_uri | An EIP-681 URI for wallets and QR codes, requesting exactly total of the token. |
required_confirmations | The confirmation threshold that applies to this invoice. See Confirmations. |
expires_at | End of the payment window. |
merchant | { "reference", "name" } of the seller. |
splits | [{ "account", "name", "percent", "amount" }]; amount is null until a pair is selected. |
title, description, merchant_order_id, redirect_url, metadata, buyer_email | As sent. |
payment_link_id | The payment link that created the invoice (plink_…), or null for an invoice created with this endpoint. |
pay_url | The hosted checkout: https://aliquot.cc/pay/{id}. |
created_at, detected_at, paid_at, cancelled_at | Timestamps; null until they happen. |
Statuses
| Status | Meaning | Webhook on entry |
|---|---|---|
AWAITING_SELECTION | Waiting for the buyer to pick a network and token. | invoice.created (at creation) |
PENDING | Address assigned, waiting for a transfer. | invoice.created (at creation) |
DETECTED | A transfer was seen and is confirming. Does not expire. | invoice.detected |
PAID | Confirmed, and the amount covers the total. | invoice.paid |
OVERPAID | Confirmed with more than the total; the excess is refundable. | invoice.paid and invoice.overpaid |
UNDERPAID | Confirmed but short. The buyer can send the rest before expires_at. | invoice.underpaid |
EXPIRED | The window ended with nothing received. | invoice.expired |
EXPIRED_UNDERPAID | The window ended with a partial payment, held for the buyer as a refund. | invoice.expired (and refund.claimable) |
REFUNDED | The partial payment was returned to the buyer. | refund.completed |
CANCELLED | You voided it. | None |
The full lifecycle diagram is in the Overview.
Retrieve an invoice
GET /api/v1/crypto/invoices/{id} — permission crypto-invoices:read.
Returns the invoice object plus:
| Field | Description |
|---|---|
idempotency_key | The Idempotency-Key it was created with (null for invoices started from a payment link). |
transfers | [{ "tx_hash", "token", "amount", "confirmations", "required", "status", "block_number" }]. status is SEEN (confirming), CONFIRMED or ORPHANED (its block was replaced by a reorg). token differs from the invoice's for a wrong-token payment. |
ledger | Once PAID or OVERPAID: the settlement posting, { "id", "entries": [{ "side", "account", "amount" }] }. See Ledger and splits. |
refunds | Refunds held for the buyer of this invoice, with claim_url while claimable. See Refunds. |
List invoices
GET /api/v1/crypto/invoices — permission crypto-invoices:read.
| Query parameter | Description |
|---|---|
mode | test or live. Defaults to your key's environment (live with a legacy key). |
status | One status from the table above. |
q | Search by invoice ID, deposit address, transaction hash or merchant_order_id. |
cursor, limit | Pagination; limit 1–100, default 25. |
curl "https://api.aliquot.cc/api/v1/crypto/invoices?mode=test&q=order-77310" \
-H "Authorization: Bearer $ALIQUOT_API_KEY"The response is { "items": [...], "next_cursor": "..." }, newest first.
Void an invoice
POST /api/v1/crypto/invoices/{id}/void — permission crypto-invoices:update. No body.
Voiding moves an AWAITING_SELECTION or PENDING invoice to CANCELLED and returns it. Any other status answers 409 CONFLICT: once a payment is detected, the invoice can no longer be voided. Voiding sends no webhook.
If the buyer pays a cancelled invoice anyway, the money is not credited to the seller; it is held for the buyer as a LATE_PAYMENT refund.
Quote
POST /api/v1/crypto/quote — permission crypto-invoices:read. Computes the same breakdown an invoice would get, without creating anything.
{
"amount": "100.00",
"network": "POLYGON_AMOY",
"token": "USDC",
"fee_payer": "CUSTOMER",
"markup_percent": 2,
"merchant": "halden_type",
"splits": [{ "account": "kerning_weekly", "percent": 10 }]
}network and token are required; fee_payer, markup_percent, merchant and splits are optional and default as on an invoice. The response:
{
"network": "POLYGON_AMOY",
"token": "USDC",
"decimals": 6,
"fee_payer": "CUSTOMER",
"base": "100.00",
"platform_fee": "0.50",
"markup": "2.00",
"merchant_net": "90.00",
"total": "102.50",
"splits": [{ "account": "kerning_weekly", "percent": 10, "amount": "10.00" }]
}The hosted checkout
Send the buyer to pay_url (https://aliquot.cc/pay/{id}). The page needs nothing else from you.
- Network selection. For an
AWAITING_SELECTIONinvoice, the buyer first picks one of the network and token pairs available in the invoice's environment. Picking assigns the deposit address, computestotalfor that token, and restarts the payment window: the buyer gets the fullexpires_in_minutesfrom that moment. A pair cannot be changed once picked. - Paying. The buyer sees the exact amount, the address and a QR code (the EIP-681
payment_uri), and can pay from a browser wallet with one button, which switches the wallet to the right network first. - Live status. The page follows the invoice as it moves through
DETECTED, with a confirmation count, toPAID, and then shows a receipt, with the payment link's fulfilment when there is one. If the buyer underpays, the page asks for the rest; if the invoice expires or a refund is held, the page says so.
The checkout is built on public endpoints you can also call to build your own payment page:
| Endpoint | Description |
|---|---|
GET /api/v1/public/pay/{id} | The buyer-safe view of the invoice: amounts, address, confirmations, allowed_pairs, refund status, and fulfillment (null until the invoice is paid; see Payment links). No ledger data, no internal IDs. |
POST /api/v1/public/pay/{id}/select | Body { "network", "token" }. Picks the pair of an AWAITING_SELECTION invoice. 409 if a different pair was already picked or the invoice expired. |
GET /api/v1/public/pay/{id}/stream | Server-sent events. An invoice event carries the same view as GET /api/v1/public/pay/{id} on every change. The stream closes when the invoice reaches PAID, OVERPAID, EXPIRED, EXPIRED_UNDERPAID, REFUNDED or CANCELLED, and after 30 minutes otherwise; EventSource reconnects on its own. |
const source = new EventSource('https://api.aliquot.cc/api/v1/public/pay/inv_01JB7Q8M3X4K9TPZ2R6WVN5HDC/stream');
source.addEventListener('invoice', (e) => {
const view = JSON.parse(e.data);
console.log(view.status, `${view.confirmations}/${view.required_confirmations}`);
});Anyone with the invoice ID can open its checkout, so do not treat the ID as a secret, and never fulfil an order from what a browser reports. Fulfil from the invoice.paid webhook or from GET /api/v1/crypto/invoices/{id}.
Returning to your site
When the checkout sees the invoice reach PAID while the buyer has it open, it counts down five seconds and navigates to redirect_url; the buyer can choose to stay. A buyer who opens the page after the fact gets a link back instead, and so does a buyer whose receipt shows a payment link's fulfilment, so they do not miss it. Aliquot does not append anything to the URL, so put whatever you need to find the order in redirect_url itself, for example https://shop.example.com/orders/77310.
Arriving at redirect_url does not prove payment. Check the invoice status on your server.
Payment window
expires_in_minutes (default 30) is the payment window: how long the buyer has to send the money once a deposit address exists.
- Network and token sent at creation. The window starts at creation:
expires_atis the creation time plusexpires_in_minutes. - The buyer picks the network.
expires_atfirst bounds the choice: the buyer must pick a pair withinexpires_in_minutesof creation, or the invoice becomesEXPIRED. Picking movesexpires_atto the moment of selection plus the sameexpires_in_minutes, so the buyer always gets the full window to pay, and the whole flow can take up to twiceexpires_in_minutes.
Invoices created from a payment link use the organization's default window (30 minutes) the same way. An invoice that is still PENDING or AWAITING_SELECTION at expires_at becomes EXPIRED; one that is UNDERPAID becomes EXPIRED_UNDERPAID. A DETECTED invoice never expires, because the buyer paid in time.