Skip to main content

Orders API

This document covers the Orders endpoints used to create and manage crypto invoice orders (deposit address + payment URI + QR + lifecycle status), including refunds for cancelled orders. It includes:
  • List orders (GET /api/orders)
  • Create an invoice order (POST /api/orders)
  • Delete an order by reference (DELETE /api/orders?reference=...)
  • Fetch a single order + payment notifications (GET /api/orders/:orderId)
  • Update an order status (PATCH /api/orders/:orderId) — including webhook dispatch + forward funds
  • Refund a cancelled order (POST /api/orders/:orderId/refund)
These endpoints are protected, you must be authenticated.

Base URL

All examples use:
  • https://www.niftipay.com
If you’re running locally, replace with your own base URL.

Authentication

These endpoints support two authentication methods (depending on your integration setup): Send your API key in the x-api-key header.
curl -X GET "https://www.niftipay.com/api/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"
If you are authenticated via the dashboard..
For browser calls, you usually don’t need to add headers manually — the cookie is sent automatically.

Order statuses

Crypto invoice orders use these statuses:
  • pending — waiting for payment (default on creation)
  • paid — fully paid (or manually marked paid)
  • cancelled — cancelled (manual or automated)
  • refunded — refund executed and wallet freed

Chain + asset rules

network and asset work like this:
  • network is the chain where the deposit address lives: BTC | LTC | ETH | SOL | XRP
  • asset is what the customer sends:
    • Native coin (e.g. BTC, ETH, SOL)
    • Or an ERC‑20 token on Ethereum for supported tokens (e.g. USDT, USDC) only when network is ETH
When the API returns amounts, it may include:
  • amount — base invoice amount
  • networkFees — any network fee the customer must add (nullable)
  • totalToSendamount + networkFees (or just amount if fees are null)

Payment URI + QR URL

For convenience, orders return:
  • paymentUri — a standard chain-specific URI (bitcoin:, ethereum:, solana:, ripple:, etc.)
  • qrUrl — a QR image URL generated from paymentUri
Example formats:
  • BTC: bitcoin:<address>?amount=<totalToSend>
  • LTC: litecoin:<address>?amount=<totalToSend>
  • SOL: solana:<address>?amount=<totalToSend>
  • XRP: ripple:<address>?amount=<totalToSend>&dt=<destinationTag?>
  • ETH native: ethereum:<address>?amount=<totalToSend>
  • ETH token: ethereum:<address>?contract=<tokenContract>&amount=<totalToSend>

List Orders

Endpoint

GET /api/orders Returns a list of orders for the authenticated user, newest first.

Query parameters

NameTypeDescription
statusstringFilter by order status (e.g. pending, paid, cancelled, refunded).
referencestringFilters orders where reference starts with this value.
searchstringSearches in firstName, lastName, and email.

Example request: list all orders

curl -X GET "https://www.niftipay.com/api/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example request: filter by status

curl -X GET "https://www.niftipay.com/api/orders?status=pending" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example request: filter by reference prefix

curl -X GET "https://www.niftipay.com/api/orders?reference=INV-" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example request: search by customer name/email

curl -X GET "https://www.niftipay.com/api/orders?search=john" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "orders": [
    {
      "id": "a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91",
      "reference": "INV-10001",
      "network": "ETH",
      "asset": "USDT",
      "amount": "68",
      "networkFees": "0.5",
      "totalToSend": "68.5",
      "currency": "EUR",
      "firstName": "John",
      "lastName": "Smith",
      "email": "john@example.com",
      "addressId": "a6a71a4c-1f6d-4c56-b3a2-3a52dbe8b3d3",
      "destinationTag": null,
      "status": "pending",
      "createdAt": "2026-02-11T10:00:00.000Z",
      "expiresAt": "2026-02-11T22:00:00.000Z",
      "paidAt": null,
      "txId": null,
      "blockNumber": null
    }
  ]
}

Create Order (Crypto Invoice)

Endpoint

POST /api/orders Creates a crypto invoice order:
  • reserves a deposit address
  • computes the amount in crypto/token based on your fiat amount
  • returns paymentUri and qrUrl for checkout UX
  • starts in pending

Important behavior

  • Payment method gating: if crypto invoices are disabled for your user, this endpoint returns 403.
  • Reference handling:
    • If you provide a reference, it must be unique for your user.
    • If you don’t provide a reference, one is generated (UUID).
    • If you provide a reference that conflicts with a fiat order merchantReference, it returns 409.
  • Idempotency (optional):
    • If you pass Idempotency-Key (or x-idempotency-key) and you retry with the same reference, the API can return the existing order instead of failing.
  • Replace cancelled (optional):
    • Add ?replaceCancelled=1 to allow re-using a reference only when the prior order is cancelled (and the old order is safely deletable).

Request body

{
  "network": "ETH",
  "asset": "USDT",
  "amount": 68,
  "currency": "EUR",
  "firstName": "John",
  "lastName": "Smith",
  "email": "john@example.com",
  "reference": "INV-10001",
  "merchantId": "optional-merchant-id",
  "pct": "optional-fee-percent-or-flag"
}
Fields
FieldTypeRequiredNotes
networkstringOne of BTC, LTC, ETH, SOL, XRP.
assetstringAsset symbol. For USDT/USDC you must use network=ETH.
amountnumberFiat invoice amount.
currencystringDefaults to EUR.
firstNamestringCustomer name.
lastNamestringCustomer name.
emailstringUsed for receipts and internal processing.
referencestringIf omitted, a UUID is generated. Must be unique for this user.
merchantIdstringYour internal merchant identifier (if you use multi-merchant).
pctstring/numberPassed through to invoice creation logic.

Example: create an ETH USDT invoice

curl -X POST "https://www.niftipay.com/api/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "network": "ETH",
    "asset": "USDT",
    "amount": 68,
    "currency": "EUR",
    "firstName": "John",
    "lastName": "Smith",
    "email": "john@example.com",
    "reference": "INV-10001"
  }'

Example response (201)

{
  "order": {
    "id": "a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91",
    "reference": "INV-10001",
    "network": "ETH",
    "asset": "USDT",
    "amount": "68",
    "networkFees": "0.5",
    "totalToSend": "68.5",
    "currency": "EUR",
    "firstName": "John",
    "lastName": "Smith",
    "email": "john@example.com",
    "address": "0xabc123...depositAddress",
    "destinationTag": null,
    "paymentUri": "ethereum:0xabc123...depositAddress?contract=0xdAC17F958D2ee523a2206206994597C13D831ec7&amount=68.5",
    "qrUrl": "https://api.qrserver.com/v1/create-qr-code?size=300x300&data=ethereum%3A0xabc123...depositAddress%3Fcontract%3D0xdAC17F958D2ee523a2206206994597C13D831ec7%26amount%3D68.5",
    "status": "pending",
    "createdAt": "2026-02-11T10:00:00.000Z",
    "expiresAt": "2026-02-11T22:00:00.000Z",
    "merchantId": null
  }
}

Idempotency example

If you’re creating orders from a backend and might retry on network failure, do:
  • Include a stable reference
  • Include an idempotency key
curl -X POST "https://www.niftipay.com/api/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: 8f0c8c92-8c4f-4e66-9f8f-5e6f98d2817a" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "network": "BTC",
    "asset": "BTC",
    "amount": 25,
    "currency": "EUR",
    "firstName": "John",
    "lastName": "Smith",
    "email": "john@example.com",
    "reference": "INV-RETRY-1"
  }'
If the same reference already exists and idempotency headers are present, the API returns the existing order (200) instead of a 409 conflict.

Replace cancelled example

If you want to re-use a reference that exists only because the prior invoice was cancelled:
curl -X POST "https://www.niftipay.com/api/orders?replaceCancelled=1" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "network": "ETH",
    "asset": "ETH",
    "amount": 10,
    "currency": "EUR",
    "firstName": "John",
    "lastName": "Smith",
    "email": "john@example.com",
    "reference": "INV-CANCELLED-1"
  }'

Delete Order (by reference)

Endpoint

DELETE /api/orders?reference=<reference> Deletes a crypto invoice order by reference, if it is safe to delete.

When deletion is blocked

The API checks wallet balances and blocks deletion if the deposit wallet has any funds. It also deletes:
  • per-order subscriptions (best effort)
  • paymentNotification rows
  • frees the deposit wallet (isAssigned=false)
  • deletes the order row

Example request

curl -X DELETE "https://www.niftipay.com/api/orders?reference=INV-10001" \
  -H "x-api-key: YOUR_API_KEY"

Success response

  • 204 No Content

Error responses

Missing reference
{ "error": "reference query-param required" }
Not found
{ "error": "Order not found" }
Wallet has balance (cannot delete)
{ "error": "Cannot delete – wallet has 0.01 BTC" }

Fetch Order (by ID) + Payments

Endpoint

GET /api/orders/:orderId Returns:
  • the order
  • payment notifications (payments)
  • a computed runningTotal (sum of payment notifications)
  • a computed remaining on-chain balance (best effort)
  • a normalized fees object (snapshot)

Example request

curl -X GET "https://www.niftipay.com/api/orders/a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "order": {
    "id": "a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91",
    "reference": "INV-10001",
    "userId": "user-id",
    "merchantId": null,
    "chain": "ETH",
    "asset": "USDT",
    "amount": "68",
    "networkFees": "0.5",
    "totalToSend": "68.5",
    "currency": "EUR",
    "firstName": "John",
    "lastName": "Smith",
    "email": "john@example.com",
    "addressId": "a6a71a4c-1f6d-4c56-b3a2-3a52dbe8b3d3",
    "depositAddress": "0xabc123...depositAddress",
    "destinationTag": null,
    "status": "pending",
    "createdAt": "2026-02-11T10:00:00.000Z",
    "expiresAt": "2026-02-11T22:00:00.000Z",
    "paidAt": null,

    "runningTotal": "0",
    "remaining": "0",
    "paymentUri": "ethereum:0xabc123...depositAddress?contract=0xdAC17F958D2ee523a2206206994597C13D831ec7&amount=68.5",
    "qrUrl": "https://api.qrserver.com/v1/create-qr-code?size=300x300&data=...",
    "fees": {
      "snapshotVersion": 1,
      "snapshotAt": "2026-02-11T10:00:00.000Z",
      "platformFeePercent": 2.5,
      "platformFeeAmount": "1.7",
      "merchantNetAmount": "66.3",
      "customerPayAmount": "68.5"
    }
  },
  "payments": [
    {
      "amount": "10",
      "txId": "0xdeadbeef...",
      "blockNumber": "19876543",
      "createdAt": "2026-02-11T10:02:00.000Z",
      "counterAddress": "0xsender..."
    }
  ]
}
Notes:
  • payments are returned newest first.
  • runningTotal is the sum of positive paymentNotification.amount.
  • remaining is a best-effort on-chain balance read for the deposit wallet.

Update Order Status (manual)

Endpoint

PATCH /api/orders/:orderId Allows manual transitions between statuses. Valid statuses:
  • pending
  • paid
  • cancelled

Request body

{
  "status": "paid"
}
FieldTypeRequiredNotes
statusstringMust be one of pending, paid, cancelled.

Important behavior

Idempotency for status changes

If you PATCH the same status the order already has, the API returns success and a warning, and performs no side effects:
  • no webhook dispatch
  • no forward funds
  • no subscription changes
Example response:
{
  "success": true,
  "warnings": [
    {
      "code": "no_change",
      "message": "Order is already \"paid\". No action taken."
    }
  ]
}

Side effects by status

If status = paid

  • sets paidAt = now
  • sends a merchant webhook event "paid" (best effort)
  • attempts to forward funds for non-XRP invoices (best effort)
  • attempts to delete the per-order subscription (best effort)
If forwarding fails, the order stays paid and you get a warning:
{
  "success": true,
  "warnings": [
    {
      "code": "forward_funds_failed",
      "message": "Funds could not be forwarded (likely dust/fee or zero balance). Payment is marked paid and webhook was sent."
    }
  ]
}
If the order is XRP, forwarding is intentionally skipped (destination-tag model):
{
  "success": true,
  "warnings": [
    {
      "code": "forward_skipped_xrp",
      "message": "Forwarding is skipped for XRP destination-tag invoices."
    }
  ]
}

If status = pending

  • clears paidAt and refundedAt
  • sets a new expiresAt = now + 12 hours
  • ensures deposit wallet is assigned (isAssigned=true)
  • attempts to recreate a subscription (best effort, non-XRP)
  • sends a merchant webhook event "pending" (best effort)

If status = cancelled

  • attempts to delete subscription (non-XRP)
  • checks balance, and if balance is "0" it frees the wallet (isAssigned=false)
  • sends a merchant webhook event "cancelled" (best effort)

Example: mark an order as paid

curl -X PATCH "https://www.niftipay.com/api/orders/a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "status": "paid" }'

Example response

{ "success": true, "warnings": [] }

Example: revert an order to pending

curl -X PATCH "https://www.niftipay.com/api/orders/a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "status": "pending" }'

Example: cancel an order

curl -X PATCH "https://www.niftipay.com/api/orders/a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "status": "cancelled" }'

Refunds

Refunds are performed from the deposit wallet back to a user-provided refundAddress. Refunds are only allowed when:
  1. the order exists and belongs to you
  2. the order is cancelled
  3. there is a non-zero balance to refund
After a successful refund:
  • order status becomes refunded
  • refundedAt is set
  • the deposit wallet is freed (isAssigned=false)
  • a merchant webhook event "refunded" is sent (best effort)

Refund an Order

Endpoint

POST /api/orders/:orderId/refund

Request body

{
  "refundAddress": "YOUR_WALLET_ADDRESS"
}
FieldTypeRequiredNotes
refundAddressstringDestination address/account to receive the refund.

Example: refund a cancelled ETH order

curl -X POST "https://www.niftipay.com/api/orders/a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91/refund" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "refundAddress": "0xYourRefundAddressHere" }'

Example success response

{
  "success": true,
  "order": {
    "id": "a9a3c588-9a6c-4e44-9b21-5c8d1f4f2b91",
    "reference": "INV-10001",
    "status": "refunded",
    "refundedAt": "2026-02-11T11:00:00.000Z",
    "chain": "ETH",
    "asset": "USDT"
  }
}
The returned order is the order row after it was updated to refunded.

Refund rules by chain/asset (high level)

The refund implementation attempts to send a net amount (after accounting for chain rules/fees) from the deposit wallet:
  • ERC‑20 on ETH (USDT/USDC):
    • token amount is refunded in full
    • ETH is required in the deposit wallet to pay gas
    • the system may top-up ETH gas automatically (internal) to enable the refund
  • Native ETH:
    • refunds balance - gasFee (fails if too small after fees)
  • BTC / LTC:
    • refunds balance - estimatedFee (and may broadcast via txData if needed)
    • very small amounts may not refund if below dust/fee thresholds
  • SOL:
    • keeps a small reserve (rent + fee) and refunds the remainder
  • XRP:
    • keeps the account reserve (e.g. 10 XRP) and refunds the remainder

Error responses

Missing refund address (400)

{ "error": "Refund address is required" }

Not found (404)

{ "error": "Not found" }

Order is not cancelled (400)

{ "error": "Refund allowed only for cancelled orders" }

Nothing to refund (400)

{ "error": "Nothing to refund" }

Refund failed (502)

{ "error": "Refund failed – see logs" }

Webhooks (merchant notifications)

When you manually transition status via PATCH /api/orders/:orderId, the API attempts to notify your webhook endpoint (best effort) with:
  • "pending"
  • "paid"
  • "cancelled"
When a refund succeeds via POST /api/orders/:orderId/refund, the API also sends:
  • "refunded"
Refund webhook payload includes:
  • id, reference, merchantId
  • refundedAt
  • amount (the actual net amount sent on-chain)
  • chain, asset
If webhook dispatch fails, the API still completes the operation and returns success.

Error codes summary

These endpoints commonly return:
  • 400 — invalid payload, invalid status, missing required query param, nothing to refund
  • 403 — payment method disabled (crypto invoices disabled)
  • 404 — order not found
  • 409 — reference conflict, wallet has balance, no available addresses
  • 502 — refund broadcast failed (upstream/provider errors)