---
title: Invoices
description: Create crypto payment invoices with the Coinetic API: request fields, allowed assets, idempotency, invoice statuses, amounts, exchange rates and late payments.
url: https://coinetic.money/docs/invoices/
lang: en
---

# 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](https://coinetic.money/docs/authentication/)), `Idempotency-Key`, `Content-Type: application/json`.

`request body`

```json
{
  "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" }
}
```

| Field | Required | Rules |
| --- | --- | --- |
| `order_id` | yes | 1–128 characters, unique per merchant (a duplicate returns `409 duplicate_order`) |
| `price_amount` | yes | Decimal **string** > 0, up to 18 decimal places |
| `price_currency` | yes | `USD`, `EUR`, `USDT` or `USDC` |
| `allowed_assets` | no | Up 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_url` | no | `https://…`, used for the “Return to store” button |
| `expires_in_seconds` | no | 300–86,400; defaults to 1800 (30 minutes) |
| `metadata` | no | Up 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`

```json
{
  "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

| Status | Meaning | What your store should do |
| --- | --- | --- |
| `created` | Invoice created, no currency selected yet | wait |
| `awaiting_payment` | Currency selected, address issued | wait |
| `detected` | Payment seen on the network (mempool) | show “payment detected,” **don't fulfill** |
| `confirming` | In a block, fewer confirmations than required | show progress, **don't fulfill** |
| `paid` | Paid in full and confirmed | **fulfill the order** |
| `overpaid` | Paid more than required | fulfill the order; refund the excess on request |
| `underpaid` | Confirmed amount is short (after tolerance) | ask the customer to top up to the same address |
| `expired` | Expired with no payment | offer a new invoice (see below) |
| `late_payment` | Payment arrived after expiration | your call: fulfill or refund |
| `manual_review` | Payment under AML review | wait for the decision; don't fulfill |
| `refunded`, `cancelled` | Closed | don't fulfill |

> **The golden rule.** Fulfill 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.
