CoinsFlow
API Referencev1

CoinsFlow Payments API

Create invoices, send customers to a hosted payment page, and track payments on five blockchains: Bitcoin, Litecoin, Ethereum, BNB Smart Chain and TRON, including USDT and USDC. JSON over HTTPS; no SDK required.

RESTJSONBearer authIdempotentBTCLTCETHBNBTRXUSDTUSDC
For AI agents & LLM integrations

Base URL: https://api.coinspath.net

Auth: header Authorization: Bearer cf_live_…. Keys come from https://coinspath.net/apis/dashboard.

Amounts: always decimal strings, e.g. "0.015". Never JSON numbers.

Endpoints: POST /v1/invoices · GET /v1/invoices/{id} · GET /v1/invoices · POST /v1/invoices/{id}/cancel · GET /v1/balances · GET /v1/me · GET /v1/stats · GET /v1/withdrawals/quote · POST /v1/withdrawals · GET /v1/withdrawals/{id} · GET /v1/withdrawals · POST /v1/withdrawals/{id}/cancel · /v1/webhooks · GET /chains · GET /prices · GET /status

Webhooks: header CoinsFlow-Signature: t=…,v1=…; v1 = hex HMAC-SHA256(secret, "<t>.<raw body>"). Dedupe on the event id.

Statuses: new invoices are pending; fulfil at paid, overpaid or settled; detected is not paid yet.

Machine-readable: /openapi.json (OpenAPI 3.1) · /llms.txt

Getting started

  1. 1Create an account and add a merchant (one per store). Copy the API key it shows you; it is shown only once.
  2. 2From your server, POST /v1/invoices with the chain, asset and price.
  3. 3Redirect the customer to the paymentUrl in the response. It shows the address, amount, a QR code and live status.
  4. 4Check the invoice with GET /v1/invoices/{id}. Fulfil the order once its status is paid, overpaid or settled.
curl https://api.coinspath.net/v1/invoices \
  -H "Authorization: Bearer $COINSFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "chain": "tron",
    "asset": "USDT",
    "priceAmount": "49.99",
    "orderId": "order-1042",
    "description": "Pro plan, 1 month"
  }'

Authentication

Send your key as a Bearer token on every request. Keys look like cf_live_<prefix>_<secret>.

http
Authorization: Bearer cf_live_3f9a0c1d2e4b5a6f_9sQ…

We store only a hash of the secret, so a lost key cannot be shown again: rotate it in the dashboard, and the old key stops working immediately. Suspended merchants' keys are refused with 401. Keys cannot withdraw unless you allow it, and can be locked to your server's IP addresses: see Keys, permissions and IP allowlists.

Keep it server-side. Never put your key in browser JavaScript, a mobile app or a public repository. Anyone holding it can create invoices and read your balances (and, if you enabled it, withdraw from an allowed IP).

Base URL & format

text
https://api.coinspath.net

Requests with a body must send Content-Type: application/json. Responses are JSON. Times are ISO 8601 in UTC.

Amounts

Every amount, in requests and responses, is a decimal string in whole units of the asset: "0.015" BTC, "49.99" USDT. Never send or parse them as JSON numbers; floating point cannot represent them exactly.

Give either amount (exact crypto) or priceAmount (USD). With a USD price we lock the current rate when the invoice is created and round the crypto amount up by at most one base unit, so you are never short.

Chains & assets

Use the chain id exactly as written. An invoice is paid at its confirmationsRequired, and settled (withdrawable) at the chain's final depth.

chainassetsblock timepayment seenpaid at (≤ $500 / above)settled atdecimals
bitcoinBTC~10 minseconds (mempool)1 / 268
litecoinLTC~2.5 minseconds (mempool)2 / 6128
ethereumETH · USDT · USDC12 sfirst block1232ETH 18 · tokens 6
bscBNB · USDT · USDCunder 1 sfirst block153018 (all, incl. USDT/USDC)
tronTRX · USDT · USDC3 sfirst block20406

USDT and USDC exist on three networks. They are different tokens with different contracts: a customer paying a tron USDT invoice must send TRC-20 USDT. The hosted page warns them about this.

How a payment flows

  1. 1 · pending
    You create the invoice and send the customer to paymentUrl.
  2. 2 · detected
    The customer sends. On Bitcoin and Litecoin we see it in the mempool within seconds; on the other chains in its first block.
  3. 3 · paid
    It reaches confirmationsRequired and is credited to your pending balance. Fulfil here.
  4. 4 · settled
    It reaches final depth and becomes withdrawable.

Typical time from sending to paid:

chaininvoice ≤ $500invoice above $500
bitcoin~10 min (1 conf.)~20 min (2 conf.)
litecoin~5 min (2 conf.)~15 min (6 conf.)
ethereum~2.5 min~2.5 min
bsc~15 s~15 s
tron~1 min~1 min

Small invoices on the two slow chains need fewer confirmations because reorganising even one block to steal them would cost far more than they are worth. Withdrawal always waits for the chain's full final depth, whatever the invoice.

What the customer pays in network fees (the fee is theirs, on top of the invoice amount):

network · assetfeenotes
litecoin · LTCVery lowUsually a fraction of a cent.
bsc · BNB, USDT, USDCVery lowCents or less, paid in BNB.
ethereum · ETH, USDT, USDCVariesPaid in ETH; token transfers cost more than plain ETH.
bitcoin · BTCVariesDepends on how busy the network is; usually low today.
tron · TRXLowCovered by free bandwidth for most wallets.
tron · USDT, USDCNoticeableBurns energy: about 6.5 to 13 TRX per transfer unless the sender holds staked energy.

For the lowest customer fees, offer Litecoin or BNB Smart Chain. Our addresses are native SegWit on Bitcoin and Litecoin, the cheapest kind to send to.

Invoice lifecycle

statusmeaning
pendingCreated, waiting for the customer. Nothing sent yet.
detectedA payment is on the network. On Bitcoin and Litecoin that is seconds after the customer sends it (still in the mempool); elsewhere, in its first block. Not money yet.
confirmingIn a block, gaining confirmations.
underpaidConfirmed, but less than amountDue arrived (0.5% tolerance). The customer can top up to the same address.
paidReached confirmationsRequired and credited to your pending balance. Safe to fulfil the order.
overpaidLike paid, but more than amountDue arrived. The full amount is credited to you.
settledReached full finality. The amount is withdrawable.
expiredDeadline passed with nothing sent. Every address we issue is watched permanently, so a payment that arrives late is still detected and credited (paidLate: true).
cancelledCancelled by you before any payment. Anything sent to its address later is credited to you as a deposit.

A payment that is still in the mempool keeps an invoice open past its deadline: it was sent in time. If it is dropped instead of mined (replaced by fee or double-spent), the invoice goes back to pending, or to expired if its time is up.

Block reorganisations are handled for you: if a confirmed payment is reorganised out of the chain, the invoice steps back to confirming and the credit is reversed. That is why you should treat paid as final only for goods you can take back, and wait for settled for anything irreversible and large.

The invoice object

fieldtypemeaning
iduuidThe invoice id.
chain, assetstringNetwork and asset the customer must pay with.
addressstringA fresh address used by this invoice only.
amountDuedecimal stringWhat the customer must send, exactly.
amountPaiddecimal stringSum of payments that are in a block. Mempool payments are not counted.
statusstringSee Invoice lifecycle.
confirmationsRequiredintegerConfirmations at which it becomes paid. Set when created, from the chain and the invoice value.
orderId, descriptionstring | nullYours. The description is shown to the customer.
metadataobjectYour key/value pairs, returned as given. Never shown to the customer.
priceAmount, priceCurrencystring | nullThe USD price, when you priced it in USD.
usdValuestring | nullUSD value when created, for reporting.
paidLatebooleanTrue if the first payment was sent after expiresAt.
expiresAt, createdAtISO 8601UTC.
paymentUrlurlThe hosted payment page. Send the customer here.
paymentsarrayOnly on GET /v1/invoices/{id}. See Payments.

Payments

GET /v1/invoices/{id} includes payments: each on-chain transfer to the invoice address, oldest first, with txid, amount, confirmations, status, blockHeight and seenAt.

payment statusmeaning
mempoolBroadcast, not in a block yet. Shown to you and the customer; never counted or credited.
confirmingIn a block, gaining confirmations.
confirmedReached full finality.
orphanedNo longer valid. With a blockHeight, a block reorganisation removed it (it is usually re-mined within minutes). With blockHeight null, it left the mempool without being mined: replaced by fee or double-spent. Either way it is not counted.

Several payments to one invoice add up (a customer topping up after an underpayment). Anything sent to an invoice address in a different asset is still credited to you, as a deposit.

Metadata

Attach up to 20 key/value pairs to an invoice with metadata. Keys are 1 to 40 characters; values are strings (up to 500 characters), numbers, booleans or null. They come back on every read of the invoice and are never shown to the customer (unlike orderId and description, which the payment page displays).

json
"metadata": { "customerId": "c_8812", "plan": "pro", "seats": 3 }

Idempotency

Send an Idempotency-Key header (1 to 200 characters, e.g. your order id) when creating an invoice. Retrying with the same key and the same body returns the original invoice instead of creating a second one, even when retries run at the same moment: one of them creates it, the others get 409 idempotency_in_progress until it exists, then the same invoice. The same key with a different body is refused with 409 idempotency_key_reuse. Keys are kept for 30 days.

Fulfilling orders safely

  1. Create the invoice from your server, with an Idempotency-Key (your order id works) so a retry never makes a second invoice.
  2. Store the invoice id against your order. Put your own references in orderId or metadata.
  3. Never fulfil because the customer came back from the payment page. Fetch GET /v1/invoices/{id} from your server and check status.
  4. Fulfil at paid, overpaid or settled. For something large and irreversible, wait for settled.
  5. Treat detected as “payment on its way”, not as paid. It can still be replaced or double-spent until it is in a block.
  6. Handle underpaid (ask for the rest, same address) and paidLate (the money arrived after the deadline; you decide whether to honour the price).
  7. Use webhooks to hear about payments at once, verify the signature, and still confirm with GET /v1/invoices/{id} before shipping anything valuable.

Errors

Errors return a JSON body with a stable machine-readable error code and a human detail.

json
{
  "error": "unsupported_asset",
  "detail": "USDT is not available on bitcoin. Supported: BTC."
}
statuserrormeaning
400invalid_requestBody or query failed validation; detail lists each problem.
400unsupported_assetThat asset is not available on that chain (e.g. USDT on bitcoin).
400invalid_amountAmount is not a positive decimal string with at most the asset’s decimals.
400invalid_cursorPass nextCursor back exactly as returned.
401unauthorizedMissing, wrong or revoked API key.
403merchant_inactiveThis merchant is suspended.
404not_foundNo such invoice for this merchant.
409idempotency_key_reuseThe Idempotency-Key was already used with a different body.
409idempotency_in_progressA retry arrived while the first request with that key was still running. Retry in a moment; you will get the same invoice.
409not_cancellableInvoice: a payment was already detected. Withdrawal: it is already being sent.
400invalid_destinationWithdrawal address is not valid for that network (checksum included).
400amount_too_smallBelow the minimum withdrawal for that asset.
400insufficient_balanceNot enough withdrawable (settled) balance for amount + fee; detail says how much is available.
403scope_requiredThis API key is not allowed to withdraw. Enable it in the dashboard.
403ip_not_allowedThe key has an IP allowlist and this request came from elsewhere.
403withdrawals_disabledWithdrawals are disabled for this merchant. Contact support.
503withdrawals_pausedWithdrawals are paused platform-wide for maintenance. Your balance is safe; retry later.
429(rate limited)More than 50 requests per second from one IP (bursts up to 200 are absorbed). Back off and retry.
503price_unavailableNo fresh USD price for that asset. Retry shortly, or send a crypto amount.
503chain_unavailableThat network is paused (our watcher or the network itself is delayed), so we do not hand out an address nobody is watching. Try another network or retry shortly.
500internal_errorOur fault. Safe to retry with the same Idempotency-Key.
POST/v1/invoices

Create an invoice

Reserves a fresh address from our HD wallet and returns the invoice. Returns 201.

Parameters

NameTypeDescription
chainreqstringbitcoin, litecoin, ethereum, bsc or tron.
assetreqstringBTC, LTC, ETH, BNB, TRX, USDT or USDC. Must be available on the chain.
amountstringExact crypto amount, e.g. "0.015", at most the asset’s decimals. Give this or priceAmount.
priceAmountstringPrice in USD, up to 8 decimals, e.g. "49.99". Converted at a locked rate, rounded up by at most one base unit.
orderIdstringYour reference, up to 255 characters. Returned on the invoice and shown to the customer on the payment page (as “Order”), so do not put anything private in it; use metadata for that.
descriptionstringShown to the customer on the payment page. Up to 500 characters.
ttlSecondsintegerHow long the customer has to pay. 60 to 604800; default 3600.
metadataobjectUp to 20 of your own key/value pairs. See Metadata.
Idempotency-KeyheaderMakes retries safe. See Idempotency.

Request

curl https://api.coinspath.net/v1/invoices \
  -H "Authorization: Bearer $COINSFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "chain": "tron",
    "asset": "USDT",
    "priceAmount": "49.99",
    "orderId": "order-1042",
    "description": "Pro plan, 1 month"
  }'

Response

json
{
  "id": "4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34",
  "chain": "tron",
  "asset": "USDT",
  "address": "TDCzDbXt9qSJrH9ZDyg4TBMjdTteKhQm5N",
  "amountDue": "49.99",
  "amountPaid": "0",
  "status": "pending",
  "orderId": "order-1042",
  "description": "Pro plan, 1 month",
  "metadata": { "customerId": "c_8812", "plan": "pro" },
  "priceAmount": "49.99",
  "priceCurrency": "USD",
  "confirmationsRequired": 20,
  "paidLate": false,
  "expiresAt": "2026-09-26T11:00:00.000Z",
  "createdAt": "2026-09-26T10:00:00.000Z",
  "usdValue": "49.99",
  "paymentUrl": "https://coinspath.net/invoice/4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34"
}
GET/v1/invoices/{id}

Get an invoice

The current state. amountPaid sums payments that are in a block (unconfirmed mempool payments are not counted); compare status, not amounts, to decide when to fulfil.

Parameters

NameTypeDescription
idrequuidThe invoice id.

Request

curl https://api.coinspath.net/v1/invoices/4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34 \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "id": "4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34",
  "chain": "tron",
  "asset": "USDT",
  "address": "TDCzDbXt9qSJrH9ZDyg4TBMjdTteKhQm5N",
  "amountDue": "49.99",
  "amountPaid": "49.99",
  "status": "paid",
  "orderId": "order-1042",
  "description": "Pro plan, 1 month",
  "metadata": { "customerId": "c_8812", "plan": "pro" },
  "priceAmount": "49.99",
  "priceCurrency": "USD",
  "confirmationsRequired": 20,
  "paidLate": false,
  "expiresAt": "2026-09-26T11:00:00.000Z",
  "createdAt": "2026-09-26T10:00:00.000Z",
  "usdValue": "49.99",
  "paymentUrl": "https://coinspath.net/invoice/4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34",
  "payments": [
    {
      "txid": "8f3c1a…e9d2",
      "amount": "49.99",
      "confirmations": 21,
      "status": "confirming",
      "blockHeight": 76543210,
      "seenAt": "2026-09-26T10:03:12.000Z"
    }
  ]
}
GET/v1/invoices

List invoices

Newest first. When there are more, nextCursor is set; pass it back as cursor for the next page. Cursors are stable even while new invoices are being created.

Parameters

NameTypeDescription
statusstringOne status, or several comma-separated: paid,overpaid,settled.
chainstringOnly this chain.
orderIdstringExact order id.
qstringSearch: an order id, the address paid, or the start of an invoice id.
limitinteger1 to 100; default 25.
cursorstringnextCursor from the previous page.

Request

curl "https://api.coinspath.net/v1/invoices?status=paid,overpaid,settled&chain=bitcoin&limit=50" \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "data": [ { "id": "4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34", "status": "paid", "...": "..." } ],
  "nextCursor": "MjAyNi0wOS0yNiAxMDowMDowMC4xMjM0NTYrMDB8NGYxYzllMmEt..."
}
POST/v1/invoices/{id}/cancel

Cancel an invoice

Only while nothing has been detected. After that it returns 409 not_cancellable: money is already moving and the invoice must play out.

Parameters

NameTypeDescription
idrequuidThe invoice id.

Request

curl -X POST https://api.coinspath.net/v1/invoices/4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34/cancel \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "id": "4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34",
  "chain": "tron",
  "asset": "USDT",
  "address": "TDCzDbXt9qSJrH9ZDyg4TBMjdTteKhQm5N",
  "amountDue": "49.99",
  "amountPaid": "0",
  "status": "cancelled",
  "orderId": "order-1042",
  "description": "Pro plan, 1 month",
  "metadata": { "customerId": "c_8812", "plan": "pro" },
  "priceAmount": "49.99",
  "priceCurrency": "USD",
  "confirmationsRequired": 20,
  "paidLate": false,
  "expiresAt": "2026-09-26T11:00:00.000Z",
  "createdAt": "2026-09-26T10:00:00.000Z",
  "usdValue": "49.99",
  "paymentUrl": "https://coinspath.net/invoice/4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34"
}
GET/v1/balances

Balances

What you hold, per chain and asset, derived from the ledger. pending is credited but not yet final; payable is final and withdrawable. Every supported pair is listed, including zeros.

Request

curl https://api.coinspath.net/v1/balances \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "currency": "USD",
  "totalUsd": "1234.56",
  "assets": [
    {
      "chain": "tron", "chainName": "TRON",
      "asset": "USDT", "assetName": "Tether", "decimals": 6,
      "pending": "49.74", "payable": "1180.02", "total": "1229.76",
      "priceUsd": 1, "valueUsd": "1229.76", "allocation": 99.6
    }
  ]
}
GET/v1/me

Merchant profile

The merchant this key belongs to. Handy for checking a key works.

Request

curl https://api.coinspath.net/v1/me \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "id": "b0c4…",
  "name": "My Shop",
  "status": "active",
  "feePercent": "0.50",
  "createdAt": "2026-09-20T12:00:00.000Z"
}
GET/v1/stats

Stats

Headline numbers for USD-priced invoices that have settled.

Request

curl https://api.coinspath.net/v1/stats \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "totalTurnover": "5120.00",
  "incomeToday": "149.97",
  "averageCheck": "42.67",
  "conversionPct": 81.3,
  "settledCount": 120,
  "totalCount": 148
}

Keys, permissions and IP allowlists

A new key can create and read invoices and read balances. It cannot move money. To withdraw over the API, open Developers in the dashboard, turn on Allow withdrawals with this key and list the IP addresses your server calls from. This needs two-factor authentication on your account, and your password.

With an allowlist set, the key is refused from any other address on every endpoint with 403 ip_not_allowed. Single addresses and CIDR blocks work, IPv4 and IPv6. Rotating the key keeps its permissions and allowlist.

errorwhen
403 scope_requiredThe key is not allowed to withdraw. Enable it in the dashboard.
403 ip_not_allowedThe request came from an IP outside the key’s allowlist.

Withdrawals

Send your settled (withdrawable) balance to any address on the same network. The amount you ask for is what arrives; a fee, quoted up front in the same asset, is taken from your balance on top.

The fee covers every network cost of the withdrawal. Usually that is one transaction. When the funds first have to be brought together on the network (for example a token balance spread over several deposit addresses), those extra transactions are priced in too, and the quote says how many (networkTransactions). Quote with the amount and destination you will use to get the exact fee, and pass it as maxFee when you create the withdrawal: you are never charged more than that. If network fees jump after the quote so that the fee no longer covers sending, the withdrawal is not sent and the amount and fee go back to your balance; quote again. If a withdrawal fails after network fees were already spent on it (for example, a transaction that failed on chain), the amount comes back and the fee pays for those network fees.

  1. 1 · requested
    You ask. Amount + fee are reserved from your withdrawable balance.
  2. 2 · approved
    Automatically, up to your daily limit (USD 5,000 by default). Larger amounts are reviewed by our team first.
  3. 3 · broadcast
    Signed and sent, usually within a minute of approval. txid is set.
  4. 4 · confirmed
    Confirmed on chain. You get a webhook and an email.
statusmeaning
requestedReceived. Checked against your balance; the amount plus fee is reserved.
approvedApproved automatically (within your daily limit) or by our team.
signingYour balance has been debited; the transaction is being built and signed.
broadcastSent to the network. txid is set. Counting confirmations.
confirmedDone: confirmationsRequired reached.
failedCould not be completed (rejected by the network, failed on chain, or network fees rose above the quoted fee before it was sent). The amount is refunded; so is the fee, unless network fees were already spent on it. failureReason says why.
cancelledCancelled by you or by our review before anything was sent. Nothing debited.
stuckSent but not confirming as expected. Our team is alerted and resolves it; it is never refunded while it could still confirm.

Minimums (below these the fee would eat most of the amount):

assetminimum
BTC0.0002
LTC0.01
ETH0.002
BNB0.005
TRX20
USDT5
USDC5

Destination addresses are checked strictly, checksum included: a mistyped Bitcoin or TRON address, a Litecoin address on Bitcoin, or a mixed-case Ethereum address with a wrong EIP-55 checksum is refused with 400 invalid_destination before anything happens. You cannot withdraw to a CoinsFlow deposit address.

GET/v1/withdrawals/quote

Quote a withdrawal

The fee for a withdrawal made now, the minimum, and the most you can send (maxAmount: everything withdrawable, less the fee for sending that much). With amount the fee is exact for that amount; without it, it is the lowest fee (one transaction). Works with any key.

Parameters

NameTypeDescription
chainreqstringbitcoin, litecoin, ethereum, bsc or tron.
assetreqstringAn asset available on that chain.
amountstringDecimal string: the amount you want to send. Gives the exact fee for it.
destinationstringWhere it will go. On TRON a first-time receiver costs more to reach, so include it for an exact fee.

Request

curl "https://api.coinspath.net/v1/withdrawals/quote?chain=tron&asset=USDT&amount=250&destination=TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7" \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "chain": "tron",
  "asset": "USDT",
  "available": true,
  "amount": "250",
  "fee": "1.2",
  "feeUsd": "1.20",
  "feeSource": "live",
  "networkTransactions": 1,
  "minAmount": "5",
  "withdrawable": "1180.02",
  "maxAmount": "1178.82",
  "confirmationsRequired": 20
}
POST/v1/withdrawals

Create a withdrawal

Needs a key with withdrawals enabled, called from an allowlisted IP. Returns 201 with status requested. Send an Idempotency-Key: a retry then returns the same withdrawal instead of sending twice. With maxFee, a fee above it is refused with 409 fee_changed and nothing is created.

Parameters

NameTypeDescription
chainreqstringThe network to send on.
assetreqstringWhat to send.
amountreqstringDecimal string: exactly what the destination receives. The fee is taken on top.
destinationreqstringAn address on that network. Validated with its checksum.
maxFeestringThe highest fee you accept, usually the fee from your quote. Recommended.
orderRefstringYour reference, up to 255 characters. Returned on the withdrawal and in webhooks.
Idempotency-KeyheaderStrongly recommended. See Idempotency.

Request

curl https://api.coinspath.net/v1/withdrawals \
  -H "Authorization: Bearer $COINSFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-2026-09-28" \
  -d '{
    "chain": "tron",
    "asset": "USDT",
    "amount": "250",
    "destination": "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
    "maxFee": "1.2",
    "orderRef": "payout-2026-09-28"
  }'

Response

json
{
  "id": "9b2e7c10-5d4a-4f3b-8e21-6a0c1d2e3f40",
  "chain": "tron",
  "asset": "USDT",
  "destination": "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
  "amount": "250",
  "fee": "1.2",
  "total": "251.2",
  "status": "requested",
  "txid": null,
  "networkFee": null,
  "networkFeeAsset": null,
  "confirmations": 0,
  "confirmationsRequired": 20,
  "usdValue": "249.95",
  "orderRef": "payout-2026-09-28",
  "failureReason": null,
  "statusNote": null,
  "requestedAt": "2026-09-28T10:00:00.000Z",
  "approvedAt": null,
  "broadcastAt": null,
  "confirmedAt": null,
  "failedAt": null
}
GET/v1/withdrawals/{id}

Get a withdrawal

Current status, txid once sent, confirmations, and the network fee we paid. failureReason explains a failed or stuck withdrawal in plain words; statusNote explains one that is taking longer than usual.

Parameters

NameTypeDescription
idrequuidThe withdrawal id.

Request

curl https://api.coinspath.net/v1/withdrawals/9b2e7c10-5d4a-4f3b-8e21-6a0c1d2e3f40 \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "id": "9b2e7c10-5d4a-4f3b-8e21-6a0c1d2e3f40",
  "chain": "tron",
  "asset": "USDT",
  "destination": "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
  "amount": "250",
  "fee": "1.2",
  "total": "251.2",
  "status": "confirmed",
  "txid": "c4d7e1…0a9b",
  "networkFee": "13.8",
  "networkFeeAsset": "TRX",
  "confirmations": 20,
  "confirmationsRequired": 20,
  "usdValue": "249.95",
  "orderRef": "payout-2026-09-28",
  "failureReason": null,
  "statusNote": null,
  "requestedAt": "2026-09-28T10:00:00.000Z",
  "approvedAt": null,
  "broadcastAt": null,
  "confirmedAt": null,
  "failedAt": null
}
GET/v1/withdrawals

List withdrawals

Newest first, paginated like invoices with nextCursor.

Parameters

NameTypeDescription
statusstringOne status, or several comma-separated.
chainstringOnly this chain.
limitinteger1 to 100; default 25.
cursorstringnextCursor from the previous page.

Request

curl "https://api.coinspath.net/v1/withdrawals?status=requested,approved,signing,broadcast&limit=50" \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "data": [ { "id": "9b2e7c10-5d4a-4f3b-8e21-6a0c1d2e3f40", "status": "broadcast", "...": "..." } ],
  "nextCursor": null
}
POST/v1/withdrawals/{id}/cancel

Cancel a withdrawal

Only while it is requested or approved. Once it is being signed the network decides, and this returns 409 not_cancellable.

Parameters

NameTypeDescription
idrequuidThe withdrawal id.

Request

curl -X POST https://api.coinspath.net/v1/withdrawals/9b2e7c10-5d4a-4f3b-8e21-6a0c1d2e3f40/cancel \
  -H "Authorization: Bearer $COINSFLOW_API_KEY"

Response

json
{
  "id": "9b2e7c10-5d4a-4f3b-8e21-6a0c1d2e3f40",
  "chain": "tron",
  "asset": "USDT",
  "destination": "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
  "amount": "250",
  "fee": "1.2",
  "total": "251.2",
  "status": "cancelled",
  "txid": null,
  "networkFee": null,
  "networkFeeAsset": null,
  "confirmations": 0,
  "confirmationsRequired": 20,
  "usdValue": "249.95",
  "orderRef": "payout-2026-09-28",
  "failureReason": "Cancelled by you.",
  "statusNote": null,
  "requestedAt": "2026-09-28T10:00:00.000Z",
  "approvedAt": null,
  "broadcastAt": null,
  "confirmedAt": null,
  "failedAt": null
}

Webhooks

Add an endpoint in the dashboard (Developers) or with POST /v1/webhooks, and we POST a signed JSON event to it every time an invoice or withdrawal changes status. The event is queued in the same database transaction as the change, so none is ever lost.

http
POST /webhooks/coinsflow HTTP/1.1
Content-Type: application/json
User-Agent: CoinsFlow-Webhooks/1.0
CoinsFlow-Event: invoice.paid
CoinsFlow-Delivery: 5d0b8a52-6c1e-4f7a-9b3d-2e8f0a1c4b6d
CoinsFlow-Signature: t=1790590000,v1=5f3c9a0e...c21d

{
  "id": "5d0b8a52-6c1e-4f7a-9b3d-2e8f0a1c4b6d",
  "type": "invoice.paid",
  "createdAt": "2026-09-26T10:05:40.000Z",
  "previousStatus": "confirming",
  "data": {
    "object": "invoice",
    "id": "4f1c9e2a-7b3d-4c1e-9a55-2d8e6f0b1c34",
    "status": "paid",
    "chain": "tron",
    "asset": "USDT",
    "address": "TDCzDbXt9qSJrH9ZDyg4TBMjdTteKhQm5N",
    "amountDue": "49.99",
    "amountPaid": "49.99",
    "orderId": "order-1042",
    "metadata": { "customerId": "c_8812" },
    "priceAmount": "49.99",
    "priceCurrency": "USD",
    "usdValue": "49.99",
    "expiresAt": "2026-09-26T11:00:00.000Z",
    "createdAt": "2026-09-26T10:00:00.000Z",
    "settledAt": null,
    "payments": [
      { "txid": "8f3c1a…e9d2", "amount": "49.99", "confirmations": 20, "status": "confirming" }
    ]
  }
}
eventwhen
invoice.detectedA payment was seen (mempool on BTC/LTC, first block elsewhere). Not paid yet.
invoice.confirmingA payment is in a block and gaining confirmations.
invoice.paidPaid in full at the required confirmations. Fulfil the order.
invoice.overpaidMore than due arrived. Also fulfil; the surplus is credited to you.
invoice.underpaidLess than due arrived by the deadline. Ask for the rest (same address).
invoice.settledReached final depth; the funds are withdrawable.
invoice.expiredNothing arrived in time.
invoice.cancelledYou cancelled it.
withdrawal.broadcastThe withdrawal transaction was sent to the network. data.txid is set.
withdrawal.confirmedThe withdrawal is confirmed on chain.
withdrawal.failedIt could not be completed. The amount and fee are back in your balance.

data is the invoice (or withdrawal) as it is when we send, with the same fields as the API. Events can arrive out of order and more than once: use the event id to ignore repeats, and trust data.status over the event name. A ping event (from Send test) has {"type":"ping"} as its data.

Verifying signatures

Every request carries CoinsFlow-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> keyed with your endpoint's secret (whsec_…). Recompute it over the raw bytes, compare in constant time, and reject anything older than five minutes so a captured request cannot be replayed.

import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.COINSFLOW_WEBHOOK_SECRET; // whsec_...

// Verify over the RAW body: parsing and re-serialising JSON changes the bytes.
app.post("/webhooks/coinsflow", express.raw({ type: "application/json" }), async (req, res) => {
  const parts = Object.fromEntries(
    (req.get("CoinsFlow-Signature") ?? "").split(",").map((p) => p.split("=", 2)));
  const expected = crypto.createHmac("sha256", SECRET).update(`${parts.t}.${req.body}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const ok = fresh && typeof parts.v1 === "string" && parts.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  if (await alreadyHandled(event.id)) return res.sendStatus(200); // retries reuse the id
  if (event.type === "invoice.paid") await fulfilOrder(event.data.orderId, event.data);
  res.sendStatus(200); // answer quickly; do slow work in a queue
});

Delivery and retries

Answer with any 2xx within 10 seconds. Anything else (an error status, a redirect, a timeout, a refused connection) is retried:

attemptsent
1immediately
23 seconds later
330 seconds
45 minutes
51 hour
66 hours
724 hours, then it is marked failed

You can see every delivery, its response and its next retry in the dashboard, and send any of them again. An endpoint that has failed without a single success for five days is paused (turn it back on in the dashboard). Endpoints must use https:// on the public internet; we do not follow redirects and never call private or internal addresses.

endpointdoes
GET /v1/webhooksYour endpoints (the secret is shown only as a hint here).
POST /v1/webhooksAdd one: url, events (default: paid, settled, expired, withdrawal confirmed and failed), description. Returns the secret.
PATCH /v1/webhooks/{id}Change url, events, description, or enabled.
DELETE /v1/webhooks/{id}Remove it.
POST /v1/webhooks/{id}/testQueue a ping event now.
GET /v1/webhooks/{id}/deliveriesRecent deliveries with status, attempts, response. ?status=failed for failures only.
POST /v1/webhooks/{id}/deliveries/{deliveryId}/retrySend one again now.
POST /v1/webhooks/{id}/rotate-secretNew secret; the old one stops working immediately.
curl https://api.coinspath.net/v1/webhooks \
  -H "Authorization: Bearer $COINSFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://shop.example.com/webhooks/coinsflow",
    "events": ["invoice.paid", "invoice.settled", "invoice.expired", "withdrawal.confirmed", "withdrawal.failed"],
    "description": "Order fulfilment"
  }'
GET/chains

Network status (public)

No key needed. Which networks are taking payments right now. New invoices are refused with 503 chain_unavailable on a network whose status is not ok, so a customer is never given an address nobody is watching.

Request

curl https://api.coinspath.net/chains

Response

json
{
  "chains": [
    {
      "chain": "litecoin", "name": "Litecoin", "assets": ["LTC"],
      "status": "ok", "acceptingPayments": true,
      "height": 3184650, "lastBlockAt": "2026-09-26T10:50:46.000Z",
      "checkedAt": "2026-09-26T10:51:02.000Z"
    }
  ]
}
GET/prices

Prices (public)

No key needed. The latest USD price per asset, for showing estimates. Invoices priced in USD lock their own rate when created; do not compute amounts from this.

Request

curl https://api.coinspath.net/prices

Response

json
{
  "currency": "USD",
  "rates": { "BTC": "84125.00", "LTC": "73.74", "USDT": "0.9998", "...": "..." },
  "updatedAt": "2026-09-26T10:51:00.000Z"
}
GET/status

Platform status (public)

No key needed. The same data as the status page: overall state, payments per network, withdrawals and webhooks.

Request

curl https://api.coinspath.net/status

Response

json
{
  "status": "operational",
  "updatedAt": "2026-09-28T10:00:00.000Z",
  "components": [
    { "id": "api", "name": "Payments API", "status": "operational" },
    { "id": "withdrawals", "name": "Withdrawals", "status": "operational" },
    { "id": "webhooks", "name": "Webhooks", "status": "operational" }
  ],
  "chains": [
    { "chain": "litecoin", "name": "Litecoin", "status": "ok", "height": 3184650, "lastBlockAt": "2026-09-28T09:58:31.000Z" }
  ]
}

Changelog

datechange
2026-09-30Withdrawal fees now cover every network cost of the withdrawal, including any transactions needed to bring the funds together before sending. GET /v1/withdrawals/quote takes amount and destination and returns the exact fee (and networkTransactions). POST /v1/withdrawals accepts maxFee: if the fee is higher now, nothing is created (409 fee_changed). Withdrawals carry statusNote, a plain explanation while one takes longer than usual.
2026-09-28Withdrawals on all five networks (API and dashboard) with fees quoted up front, automatic approval up to a daily limit, and strict address validation. Signed webhooks for every invoice and withdrawal status, with retries for 24 hours and a delivery log. API keys can be limited to IP addresses; withdrawing over the API needs a key permission, an allowlist and two-factor. Two-factor authentication, password reset and email confirmation in the dashboard. Public GET /status.
2026-09-26Bitcoin and Litecoin payments are detected in the mempool, seconds after the customer sends them (status detected; never counted until mined). Invoices worth at most $500 are paid after 1 confirmation on Bitcoin and 2 on Litecoin. New: metadata, several statuses in one list filter, idempotency_in_progress, and a 400 for Idempotency-Keys over 200 characters instead of silently truncating them.

Coming next

Bitcoin Lightning: instant, very low-fee Bitcoin payments alongside the normal on-chain address.

Questions? Use the chat bubble at the bottom right of any page.