Skip to main content
Version: v2

Orders

Create and manage exchange orders. Status semantics and flow diagrams live in Order Lifecycle; authentication rules in Authentication.

Create Order​

POST /orders/

Request​

curl -X POST "https://api.n.exchange/en/api/v2/orders/" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "x-referral-token: YOUR_REFERRAL_TOKEN" \
-d '{
"deposit_amount": "500.00",
"deposit_currency": "USDTERC",
"withdraw_currency": "BTC",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"rate_id": "abc123-000000"
}'

Headers: Authorization required; x-referral-token recommended for attribution; X-User-Timezone optional — see Authentication.

Request fields:

FieldTypeRequiredDescription
deposit_currencystring or objectYesCurrency you send. Three formats — see currency identification
withdraw_currencystring or objectYesCurrency you receive. Same three formats
withdraw_addressstringYesDestination address. Must be non-blank and valid for the withdrawal currency's blockchain. Validation is format-level (regex/checksum per currency) — it cannot tell apart chains sharing a format (e.g. two EVM networks), so verify the address/chain pairing client-side
deposit_amountstringConditionalAmount you send (max 8 decimals). Non-DeFi orders require deposit_amount or withdraw_amount; DeFi floating/fixed orders may omit both
withdraw_amountstringConditionalAmount you receive (max 8 decimals). Same conditionality
sidestringNoBUY (default) or SELL. Crypto swaps use BUY (just omit the field); SELL is for fiat flows. DeFi orders are always BUY. Treat side in responses as informational — don't use it to classify fiat vs crypto
withdraw_address_extra_idstringNoMemo/destination tag for the withdrawal (max 127 chars)
refund_addressstringNo*Refund address — always denominated on the deposit currency's blockchain (refunds return where funds came from). Required for DeFi orders when the destination (withdraw) chain is non-EVM — currently Solana, Tron, Bitcoin
refund_address_extra_idstringNoMemo/destination tag for refund transactions (max 127 chars)
rate_idstringNoQuote id from the same GET /rate/ row. Binds order validation to that quote snapshot. Invalid/expired values fail validation
max_slippage_percentstring/nullNoWrite-only adverse-price-movement threshold for floating orders ("1.5" = 1.5%). Pattern: ^-?\d{0,3}(?:\.\d{0,4})?$. Cannot be combined with rate_id; see Slippage protection
is_defibooleanNotrue = DeFi order (requires DeFi-enabled pair/currencies, min deposit $20 USD equivalent)
is_fixed_ratebooleanNoDeFi only: fixed-rate (RFQ) mode. Must not be combined with is_limit_order
is_limit_orderbooleanNoDeFi only: limit order; requires limit_receive_amount
limit_receive_amountstringNoLimit-order minimum receive amount in human-readable destination units
limit_ttlintegerNoLimit-order TTL in seconds. Omitted/non-positive → backend default; positive values must meet the backend minimum
markupintegerNoDynamic partner fee in basis points (0–10000). Pre-agreed capability; mutually exclusive with referral_rate (both → 400)
referral_ratestringNoDeprecated — use markup. Decimal fraction, e.g. "0.005"
referral_codestringNoCeFi referral code (≤20 chars, ^[A-Za-z0-9_-]+$). Must belong to the API key owner

Slippage protection for floating orders​

max_slippage_percent sets the adverse price movement you accept between creating an order and processing its deposit. For example, "1.5" means 1.5%. On a CeFi floating order, if the market moves against the order by at least that percentage before the deposit is processed, the order is refunded instead of being filled at the worse rate.

Order modeBehavior
CeFi floatingApplies the requested threshold, subject to the validation and referral-code rules below
CeFi with rate_idRejected — remove either rate_id or max_slippage_percent
DeFi floatingApplies under DeFi's own floor and cap, replacing the configured default
DeFi limitAccepted but has no effect; the limit order's minimum received amount controls execution
DeFi fixed-rateRejected
  • The value must be greater than 0 and at most 100, with up to four decimal places. 0, negative values, and values below the configured minimum return 400.
  • If the value exceeds the referral code's slippage cap, it is clamped to that cap. If the referral code's cap is itself below the configured minimum, the request value is ignored. The field can therefore add protection to the referral code's setting, but cannot reduce that protection.
  • Omit the field to use the referral code's setting alone.
  • The field is write-only and is never returned in an order response, because echoing it could disclose the referral code's cap.

Example — CeFi floating order with 1.5% slippage protection:

{
"deposit_amount": "500.00",
"deposit_currency": "USDC",
"withdraw_currency": "BTC",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"refund_address": "0xYourUsersSourceAddress",
"max_slippage_percent": "1.5"
}

Example — object currency format with quote binding:

{
"deposit_amount": "500.00",
"deposit_currency": { "code": "USDT", "network": "ETH" },
"withdraw_currency": { "code": "BTC", "network": "BTC" },
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"refund_address": "0xYourUsersSourceAddress",
"rate_id": "waq1T39kPFn9LOETpzTnX6by7Yc-001ABB"
}

Response: 201 Created or 200 Existing Order​

StatusMeaning
201New order created. This is the default for every create request, CeFi and DeFi alike
200DeFi, legacy integrations only: an existing matching open order was returned instead of a new one
Order creation is not idempotent

By default, every POST /orders/ creates a new order with its own unique_reference, for CeFi and DeFi alike (the one exception is the legacy DeFi reuse described below). Create the order once, when the user confirms the swap, and keep the unique_reference you get back. To show a rate before that, use GET /rate/, not an order.

Do not blindly re-send a create after a client-side timeout. First check whether the first attempt succeeded, for example via GET /orders/ filtered by pair and status=INITIAL, or gate creation behind your own idempotency layer.

Floating-rate DeFi orders created with the same parameters share one deposit address (fixed-rate orders each get their own). A deposit to that address is matched to the oldest open order for those parameters, so an order created by a retry can expire unpaid while an earlier one completes.

Legacy DeFi reuse

A few integrations that predate this behaviour still receive 200 with the existing INITIAL order when a DeFi create matches one of their open orders (same pair, withdraw_address, is_fixed_rate/is_limit_order, refund_address and referral). It is not available to new integrations. Treat 200 and 201 bodies identically.

Response fields (OrderV2):

FieldTypeDescription
unique_referencestringOrder identifier for tracking (^[A-Za-z0-9_-]+$)
created_onstringISO 8601 creation timestamp
sidestringBUY or SELL (read-only)
statusstringOrder status string — canonical values in Order Lifecycle
deposit_currency / withdraw_currencystringAlways flat string codes in responses (e.g. "USDCARB"), regardless of the request format
deposit_amount / withdraw_amountstringAmounts (8 decimals); may be null on DeFi orders until execution
deposit_addressstringWhere you send funds
deposit_address_extra_idstringMemo/tag that must accompany the deposit, when required
withdraw_address / withdraw_address_extra_idstringDestination you provided
refund_address / refund_address_extra_idstringRefund destination you provided
payment_window_minutesintegerDeposit deadline window in minutes (see note below)
fixed_rate_deadlinestring/nullUTC timestamp until which the fixed rate holds
ratestringExchange rate applied to the order
deposit_transaction / withdraw_transactionstringTransaction hashes, filled as they appear
refund_transactionstringTransaction ID of the refund. Empty until refunded funds have been sent back on-chain
is_defi / is_fixed_rate / is_limit_orderbooleanMode flags
limit_expires_atstring/nullLimit-order expiry (limit orders)
limit_min_receivedstring/nullLimit-order minimum, in destination smallest units as a string integer
applied_referral_ratestringDynamic partner fee applied (only when a dynamic fee was requested)
Floating orders have no deadline

A null fixed_rate_deadline marks a floating-rate order: no rate deadline; the rate is determined when the deposit arrives. CeFi floating orders carry payment_window_minutes: 0 (a valid value, not an error); DeFi floating orders may carry a small nominal window — always key off fixed_rate_deadline. For fixed-rate orders, fixed_rate_deadline = created_on + payment_window_minutes. Countdown logic for UIs: Payment UI.

Example response (201):

{
"unique_reference": "ABCDEF123456",
"created_on": "2026-05-18T10:15:30Z",
"side": "BUY",
"status": "INITIAL",
"deposit_amount": "500.00000000",
"withdraw_amount": "0.01000000",
"deposit_currency": "USDTERC",
"withdraw_currency": "BTC",
"deposit_address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"deposit_address_extra_id": "",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"withdraw_address_extra_id": "",
"refund_address": "",
"refund_address_extra_id": "",
"payment_window_minutes": 60,
"fixed_rate_deadline": "2026-05-18T11:15:30Z",
"rate": "50000.00000000",
"deposit_transaction": "",
"withdraw_transaction": ""
}

DeFi orders​

DeFi orders use the same endpoint with is_defi: true. The deposit_address returned is a proxy address; funds sent there are swapped non-custodially and delivered on the destination chain.

DeFi floating-rate request
{
"deposit_currency": { "code": "SOL", "network": "SOL" },
"withdraw_currency": "BTC",
"withdraw_address": "bc1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"refund_address": "So1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"is_defi": true
}
  • side, deposit_amount, withdraw_amount are optional (treated as BUY; amount follows the actual deposit).
  • Minimum deposit: $20 USD equivalent; also respect the pair's min_deposit_amount from GET /rate/?is_defi=true.
  • refund_address is required when the destination (withdraw) chain is non-EVM — Solana, Tron, or Bitcoin. The address itself must be valid for the deposit currency's chain.
  • For fixed-rate DeFi, fetch a fixed-rate quote first and create the order with the same deposit amount and its rate_id.
DeFi response (note: amounts null until execution)
{
"unique_reference": "AB12CD",
"created_on": "2026-02-20T10:00:00.000000Z",
"side": "BUY",
"is_defi": true,
"is_fixed_rate": false,
"status": "INITIAL",
"deposit_currency": "SOL",
"withdraw_currency": "BTC",
"deposit_amount": null,
"withdraw_amount": null,
"deposit_address": "0xProxyAddressFromDeFi",
"withdraw_address": "bc1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"refund_address": "So1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deposit_transaction": "",
"withdraw_transaction": "",
"refund_transaction": "",
"rate": "0.0",
"payment_window_minutes": 1,
"fixed_rate_deadline": null
}
DeFi detail responses omit is_defi

GET /orders/{unique_reference}/ for a DeFi order may not include the is_defi flag itself. If your UI branches on order mode, persist the mode at creation time instead of inferring it from the detail response. See Payment UI patterns.

DeFi limit orders​

Limit orders target a minimum receive amount instead of a quoted rate. is_limit_order must not be combined with is_fixed_rate, and rate_id does not apply.

Limit order request
{
"deposit_currency": { "code": "ETH", "network": "ETH" },
"withdraw_currency": { "code": "USDT", "network": "ETH" },
"withdraw_address": "0xYourUsersWallet",
"refund_address": "0xYourUsersWallet",
"is_defi": true,
"is_limit_order": true,
"limit_receive_amount": "20",
"limit_ttl": 3600
}
  • limit_receive_amount — human-readable destination units; converted internally to smallest units (read back as limit_min_received, a string integer in smallest units).
  • limit_ttl — seconds. Omitted or non-positive → the default TTL (currently 24 hours). Positive values must be at least 600 seconds; smaller values return 400 (limit_ttl must be at least 600 seconds (10 minutes).).
  • Read back limit_expires_at for the effective expiry.

DeFi direct execution (user-signed transaction)​

For whitelisted referral codes, eligible DeFi orders also return an unsigned_transaction object next to deposit_address. Instead of transferring funds to the proxy address and waiting for the relayer, the user's wallet signs one transaction that pulls the deposit from the wallet, swaps it and delivers the output to withdraw_address. If anything fails, the whole transaction reverts and nothing leaves the wallet except gas.

Eligibility: fixed-rate orders on same-chain EVM routes. Floating-rate orders and limit orders are not offered direct execution. Ask your account manager to enable it for your referral code; without it the response is unchanged.

For a step-by-step test with calldata checks and a runnable script, see Direct Execution: Signing the Swap Yourself.

unsigned_transaction can also be missing on an eligible order, for example when no quote is available at that moment. The order is still created: use deposit_address, or request the transaction again with the refresh action below.

unsigned_transaction (inside the create response)
{
"chain_id": 8453,
"to": "0xDirectSwapEntryAddress",
"data": "0x...",
"value": "0",
"gas_estimate": null,
"gas_estimate_error": null,
"expires_at": "2026-09-09T15:12:03Z",
"quote_expires_at": "2026-09-09T15:12:33Z",
"approval": {
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"spender": "0xDirectSwapEntryAddress",
"amount": "100000000"
},
"summary": {
"rate_mode": "floating",
"proxy_address": "0xProxyAddress",
"handler_address": "0xHandlerAddress",
"entry_address": "0xDirectSwapEntryAddress",
"token_in": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"token_out": "0x4200000000000000000000000000000000000006",
"amount_in": "100000000",
"swap_input": "98000000",
"protocol_fee": "2000000",
"gas_fee": "0",
"min_amount_out": "39811234567890123456",
"destination_address": "0xYourUsersWallet"
}
}
FieldMeaning
chain_id, to, data, valueThe transaction to sign. value is the deposit amount for native input, otherwise "0".
expires_atRefresh after this time. The quote inside the calldata expires at quote_expires_at; expires_at leaves a safety margin for signing and inclusion.
approvalERC-20 allowance still needed before the transaction can be sent. null for native input, or once the sender's allowance already covers summary.amount_in.
gas_estimateSet when sender_address was supplied and the allowance is in place; gas_estimate_error carries the simulation error otherwise.
summaryAmounts are integer strings in the token's smallest units (unlike the decimal deposit_amount). Before prompting the wallet, check that summary.destination_address equals your withdraw_address and that summary.amount_in is what the wallet will send.
summary.min_amount_outThe least the transaction delivers, enforced on-chain: the transaction reverts below this amount. Compare it with the amount you show the user before prompting the wallet.
summary.rate_modeHow this transaction was priced. fixed: min_amount_out is the amount promised when the order was created. floating: the calldata carries a fresh quote and min_amount_out is that quote's floor. It is not the order's rate type; read fixed_rate_deadline on the order for that.

Flow:

  1. Create the order and keep both deposit_address and unsigned_transaction.
  2. If approval is present, have the wallet approve spender for at least amount of token.
  3. Right before prompting, refresh with POST /orders/{unique_reference}/unsigned_transaction/. Body fields are optional: sender_address (enables the allowance check and the gas estimate) and deposit_amount (a decimal string in units of the deposit currency, for example "1.25" for 1.25 USDC, not smallest units; digits beyond the currency's precision are rounded down; defaults to the amount the order was created with). Always refresh once expires_at has passed.
  4. Send the transaction from the wallet. Optionally report it with POST /orders/{unique_reference}/unsigned_transaction/submitted/ and body {"tx_hash": "0x..."} so the order completes without waiting for the chain sweep (202 means not mined yet, retry). Poll the order as usual.

The proxy deposit_address keeps working: a user who sends funds there is served by the relayer as before.

Errors on the two actions return {"detail": "...", "code": "..."}. "Refresh" is POST /orders/{unique_reference}/unsigned_transaction/ and "submit" is POST /orders/{unique_reference}/unsigned_transaction/submitted/.

CodeStatusActionMeaning
direct_execution_not_enabled409refreshDirect execution is not enabled for this referral code or on this chain.
direct_execution_unsupported_route409refresh, submitThe order is not eligible (see Eligibility above).
direct_execution_unavailable409refresh, submitDeclined for another reason. Use deposit_address.
order_not_pending409refresh, submitThe order is no longer pending (cancelled, funded or completed).
fixed_rate_expired409refreshThe fixed-rate window has passed. Create a new order.
promise_not_fillable409refreshThe promised amount cannot be filled at the current market. Create a new order.
slippage_exceeded409refreshThe rate moved beyond the order's slippage tolerance since creation.
proxy_already_funded409refreshFunds already reached deposit_address; the relayer executes that deposit.
tx_failed409submitThe transaction reverted on-chain. Nothing was swapped.
amount_required400refreshNo amount is known for the order. Send deposit_amount.
amount_invalid400refreshdeposit_amount is not a positive decimal.
tx_hash_invalid400submittx_hash is not a transaction hash.
tx_mismatch400submitThe transaction is not this order's direct execution.
refresh_rate_limited429refreshToo many refreshes. Retry after a short pause.
quote_unavailable503refreshNo quote is available right now. Retry shortly.
execution_service_unavailable503refresh, submitThe service is temporarily unavailable. Retry shortly.

The transaction can be refreshed until the order's fixed_rate_deadline. After it the refresh action returns fixed_rate_expired, and once the unfunded order is cancelled it returns order_not_pending; create a new order in both cases. A direct transaction that was signed before the cancellation and lands afterwards is still recognised and completes the order.

List Orders​

GET /orders/

Returns CeFi orders only with cursor pagination. DeFi orders do not appear in the list — track them individually via Get Order.

Request​

curl -X GET "https://api.n.exchange/en/api/v2/orders/?page_size=25" \
-H "Accept: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY"

Query Parameters:

ParameterTypeRequiredDescription
cursorstringNoOpaque cursor from pagination.next / pagination.previous
page_sizeintegerNo1–100, default 25
pairstringNoPair name filter, e.g. BTCETH
statusinteger or stringNoStatus filter — accepts the numeric code (?status=11) or the exact status name (?status=INITIAL). Mapping: Order Lifecycle. Unknown values return an empty list
Cursor, not page numbers

The list uses cursor pagination — there is no page parameter and no total count. Follow pagination.next until has_next is false. Ordering is -created_on, -id (newest first).

Response​

{
"pagination": {
"next": "https://api.n.exchange/en/api/v2/orders/?cursor=cD0yMDI2LTA1LTE3VDE0JTNBMDAlM0EwMFo%3D",
"previous": null,
"has_next": true,
"has_previous": false,
"page_size": 25,
"ordering": ["-created_on", "-id"]
},
"results": [
{
"unique_reference": "ABCDEF123456",
"created_on": "2026-05-18T10:15:30Z",
"side": "BUY",
"withdraw_amount": "0.01000000",
"deposit_amount": "500.00000000",
"deposit_currency": "USDTERC",
"withdraw_currency": "BTC",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"withdraw_address_extra_id": "",
"deposit_address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"deposit_address_extra_id": "",
"refund_address": "",
"refund_address_extra_id": "",
"payment_window_minutes": 60,
"fixed_rate_deadline": "2026-05-18T11:15:30Z",
"status": "COMPLETED",
"rate": "50000.00000000",
"deposit_transaction": "8a3f4c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f",
"withdraw_transaction": "0xfeed1234feed1234feed1234feed1234feed1234feed1234feed1234feed1234"
}
]
}

Get Order​

GET /orders/{unique_reference}/

Works for both CeFi and DeFi orders. Returns every OrderV2 field plus:

FieldTypeDescription
amount_usdnumber/nullUSD equivalent value. CeFi orders only; null/absent for DeFi
curl -X GET "https://api.n.exchange/en/api/v2/orders/ABCDEF123456/" \
-H "Accept: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY"

Poll this endpoint to track progress — recommended cadence and stop conditions in Order Tracking.

Update Order​

PATCH /orders/{unique_reference}/

All fields optional; send only what you are changing.

FieldTypeDescription
withdraw_addressstringSet the withdrawal address — only once, and only before processing starts
withdraw_address_extra_idstringMemo/tag for the withdrawal (max 127 chars)
marked_as_paidbooleantrue = mark the order paid (optionally with deposit_transaction for faster crediting); false = cancel the pending payment
deposit_transactionstringSelf-served deposit transaction hash
referral_codestringReferral code (≤20 chars; must belong to the API key owner)
curl -X PATCH "https://api.n.exchange/en/api/v2/orders/ABCDEF123456/" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY" \
-d '{
"marked_as_paid": true,
"deposit_transaction": "8a3f4c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f"
}'

Returns 200 with the updated OrderV2.

Field name

The PATCH parameter is marked_as_paid. The related read-only flag exposed on some order payloads is user_marked_as_paid — the value you set is reflected there. Marking as paid is optional: deposits are detected automatically by chain monitoring; use it (with the tx hash) to speed crediting up, or marked_as_paid: false to cancel an unpaid order.

Re-submitting a deposit_transaction that is already recorded returns a 400 with an "already exists" style validation message — safe to treat as success in retry logic. See Errors & Rate Limits.

Refund a DeFi Order​

POST /orders/{unique_reference}/refund/

Asks for the deposit of a funded DeFi order to be sent back, as long as the swap has not been executed. The usual case is a deposit below the order minimum: such an order waits for the user to send the missing amount to the same deposit address, and this call lets you return the funds instead.

  • Who can call it: the partner whose referral code is on the order (API key owner or token user), or the order's own user. For any other caller the order does not exist (404). Widget-scoped API keys cannot refund (403).
  • Where the funds go: always to the order's refund_address, or to its withdraw_address when the order was created without one. Both are fixed at creation; the request has no body and cannot redirect the refund.
  • Asynchronous: 202 means the refund was requested. The response is the order as it stands (still INITIAL). Keep polling Get Order: the order moves to INITIATED REFUND when the refund transaction is sent, then to REFUNDED with refund_transaction set.
  • Daily limit: refunds, whether you request them or they happen automatically, share a daily limit per partner. Past it, and for deposits too small to be worth their network fee, the answer is 409 and our support team handles the order.
curl -X POST "https://api.n.exchange/en/api/v2/orders/DEFI234567/refund/" \
-H "Accept: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY"
StatusMeaning
202Refund requested; body is the order (OrderV2)
403Widget-scoped API keys cannot refund; use your standard API key
404Unknown reference, not a DeFi order, or not yours
409Nothing to refund (unfunded, being executed, already executed or refunded), refund requests currently disabled, or daily refund limit reached. detail says which
503Refunds temporarily unavailable; retry later
Automatic refunds

You do not need to call this endpoint for every short deposit. A DeFi deposit that stays below the order minimum for about 10 minutes is refunded automatically, within the same daily limit. The call is for when you want the refund right away.

Errors​

StatusWhenBody shape
400Validation failure — missing fields, invalid/expired rate_id, amount outside limits, markup+referral_rate together, invalid address{"detail": "...", "errors": {"field": ["..."]}}
401Missing/invalid API key, or referral code not owned by the key owner{"detail": "..."}
404Unknown unique_reference{"detail": "Not found"}
409Refund not possible for this order right now{"detail": "..."}
503Refund temporarily unavailable{"detail": "..."}

Retryability of specific validation errors (expired quotes, transient DeFi quote failures) is catalogued in Errors & Rate Limits.

Next steps​