Docs / Integration

Invoices

An invoice represents a single purchase: a price in fiat or a stablecoin, an expiration time and, once the customer chooses, a payment currency and deposit address.

Create an invoice

POST/v1/invoices

Headers: Authorization (CP-HMAC), Idempotency-Key, Content-Type: application/json.

request body
{
  "order_id": "ORDER-1001",
  "price_amount": "49.99",
  "price_currency": "USD",
  "allowed_assets": ["USDT-TRON", "USDC-BASE", "BTC"],
  "return_url": "https://shop.example/orders/ORDER-1001",
  "expires_in_seconds": 1800,
  "metadata": { "user_id": "42", "plan": "pro" }
}
FieldRequiredRules
order_idyes1–128 characters, unique per merchant (a duplicate returns 409 duplicate_order)
price_amountyesDecimal string > 0, up to 18 decimal places
price_currencyyesUSD, EUR, USDT or USDC
allowed_assetsnoUp to 30 asset IDs (GET /v1/assets). Empty means all available. The invoice gets the intersection with available assets; if it's empty, you get 422 invalid_allowed_assets
return_urlnohttps://…, used for the “Return to store” button
expires_in_secondsno300–86,400; defaults to 1800 (30 minutes)
metadatanoUp to 20 keys [A-Za-z0-9_.-]{1,64}, values up to 500 characters; returned on the invoice

Response 201:

JSON
{
  "id": "inv_x7k2…",
  "order_id": "ORDER-1001",
  "status": "created",
  "price_amount": "49.99",
  "price_currency": "USD",
  "allowed_assets": ["USDT-TRON", "BTC"],
  "checkout_url": "https://pay.coinetic.money/pay/inv_x7k2…#t=…",
  "return_url": "https://shop.example/orders/ORDER-1001",
  "expires_at": "2026-10-12T15:30:00Z",
  "created_at": "2026-10-12T15:00:00Z",
  "quote": null,
  "paid_amount": "0", "outstanding_amount": "0", "overpaid_amount": "0",
  "refunded_amount": "0", "refundable_amount": "0",
  "payments": [],
  "metadata": { "user_id": "42", "plan": "pro" },
  "livemode": true
}
  • price_amount is returned in canonical form without trailing zeros: send "3.00" and you'll get back "3". Compare amounts as decimals, not as floats or byte strings.
  • checkout_url carries the checkout page token after the #. Don't log or publish it.
  • livemode: false marks a sandbox invoice. Your production store must reject these.

Which assets end up on the invoice

An asset is available if it's enabled in Coinetic, you have an active receiving wallet on that network, and you haven't excluded it in the dashboard (“Settings” → “Payment assets”). Each asset has a minimum (min_amount in GET /v1/assets): if the invoice amount in that asset falls below it, the asset isn't included. Example: a $3 invoice with BTC at $60,000 comes to 5,000 sats against a 10,000-sat minimum, so that invoice offers only stablecoins.

The selectable_assets field lists the allowed_assets that can be selected right now; the checkout page offers only those.

Idempotency

  • Idempotency-Key: 8–128 printable ASCII characters with no spaces, not starting with d:. Deriving it from the order number works well: order-ORDER-1001.
  • Same key, same body: same response (the same invoice).
  • Same key, different body: 409 idempotency_conflict.
  • First request still in progress: 409 idempotency_in_progress; retry in a second.
  • Business-rule rejections (such as 422) are remembered too.

Retrieve an invoice

GET/v1/invoices/{id}

Once the customer selects a currency, the invoice gets a quote:

quote
{
  "asset_id": "USDT-TRON", "network": "tron", "token_contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "address": "TX…", "amount_due": "49990000", "decimals": 6, "display_amount": "49.99",
  "payment_uri": "tron:…", "rate": "1", "rate_valid_until": null,
  "network_warning": "Send only USDT on TRON (TRC-20)…"
}

payments lists the payments: tx_hash, amount (in smallest units), confirmations, required_confirmations, state (mempool, included, confirmed, reorged, replaced, failed, dropped).

unmatched_payments lists transfers to the invoice address that were not credited: sent on a different EVM network (wrong_network) or with a non-allowlisted token (token_not_whitelisted). They don't change the status or paid_amount.

List invoices

GET/v1/invoices?status=paid&from=…&to=…&limit=50&cursor=…

Response: { "items": [...], "next_cursor": "…" | null }, newest first, limit 1–200.

Statuses

StatusMeaningWhat your store should do
createdInvoice created, no currency selected yetwait
awaiting_paymentCurrency selected, address issuedwait
detectedPayment seen on the network (mempool)show “payment detected,” don't fulfill
confirmingIn a block, fewer confirmations than requiredshow progress, don't fulfill
paidPaid in full and confirmedfulfill the order
overpaidPaid more than requiredfulfill the order; refund the excess on request
underpaidConfirmed amount is short (after tolerance)ask the customer to top up to the same address
expiredExpired with no paymentoffer a new invoice (see below)
late_paymentPayment arrived after expirationyour call: fulfill or refund
manual_reviewPayment under AML reviewwait for the decision; don't fulfill
refunded, cancelledCloseddon't fulfill
The golden ruleFulfill an order only on paid or overpaid, as returned by GET /v1/invoices/{id}.

Late stablecoin payments

USDT or USDC on a USD invoice carries no exchange-rate risk, so a late payment (up to 30 days) is credited automatically: an invoice that already received invoice.expired becomes paid, and you get invoice.paid with data.late: true. That's why you should never close an order permanently on invoice.expired. For volatile assets, a late payment results in late_payment.

Amounts and exchange rates

  • The price is set in the invoice currency. Coinetic locks the crypto amount when the customer selects a currency.
  • A stablecoin in its own currency (USDT for a USD invoice): 1:1, locked for the life of the invoice.
  • A volatile asset (BTC, ETH and others): the median of Kraken, Coinbase and Bitstamp rates, locked for 20 minutes.
  • amount_due is in the asset's smallest units (USDT on TRON has 6 decimals: "49990000" = 49.99). display_amount is the same value in standard notation.
  • The underpayment tolerance (0.5% by default) covers wallet rounding.

For a reference rate, call GET /v1/rates?base=BTC&quote=USD. Don't use it to show the customer an “exact” crypto amount: only the invoice provides a binding amount.