curl --request POST \
--url https://api-partner.houdiniswap.com/v2/exchanges \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"addressTo": "<string>",
"quoteId": "<string>",
"markup": 0.5,
"refundAddress": "<string>",
"refundExtraId": "<string>",
"intermediateRefundAddress": "<string>",
"destinationTag": "<string>",
"addressFrom": "<string>",
"signatures": [
{
"signature": "<string>",
"key": "<string>",
"swapRequiredMetadata": {}
}
],
"walletInfo": "<string>"
}
'import requests
url = "https://api-partner.houdiniswap.com/v2/exchanges"
payload = {
"addressTo": "<string>",
"quoteId": "<string>",
"markup": 0.5,
"refundAddress": "<string>",
"refundExtraId": "<string>",
"intermediateRefundAddress": "<string>",
"destinationTag": "<string>",
"addressFrom": "<string>",
"signatures": [
{
"signature": "<string>",
"key": "<string>",
"swapRequiredMetadata": {}
}
],
"walletInfo": "<string>"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
addressTo: '<string>',
quoteId: '<string>',
markup: 0.5,
refundAddress: '<string>',
refundExtraId: '<string>',
intermediateRefundAddress: '<string>',
destinationTag: '<string>',
addressFrom: '<string>',
signatures: [{signature: '<string>', key: '<string>', swapRequiredMetadata: {}}],
walletInfo: '<string>'
})
};
fetch('https://api-partner.houdiniswap.com/v2/exchanges', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-partner.houdiniswap.com/v2/exchanges",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'addressTo' => '<string>',
'quoteId' => '<string>',
'markup' => 0.5,
'refundAddress' => '<string>',
'refundExtraId' => '<string>',
'intermediateRefundAddress' => '<string>',
'destinationTag' => '<string>',
'addressFrom' => '<string>',
'signatures' => [
[
'signature' => '<string>',
'key' => '<string>',
'swapRequiredMetadata' => [
]
]
],
'walletInfo' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-partner.houdiniswap.com/v2/exchanges"
payload := strings.NewReader("{\n \"addressTo\": \"<string>\",\n \"quoteId\": \"<string>\",\n \"markup\": 0.5,\n \"refundAddress\": \"<string>\",\n \"refundExtraId\": \"<string>\",\n \"intermediateRefundAddress\": \"<string>\",\n \"destinationTag\": \"<string>\",\n \"addressFrom\": \"<string>\",\n \"signatures\": [\n {\n \"signature\": \"<string>\",\n \"key\": \"<string>\",\n \"swapRequiredMetadata\": {}\n }\n ],\n \"walletInfo\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-partner.houdiniswap.com/v2/exchanges")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"addressTo\": \"<string>\",\n \"quoteId\": \"<string>\",\n \"markup\": 0.5,\n \"refundAddress\": \"<string>\",\n \"refundExtraId\": \"<string>\",\n \"intermediateRefundAddress\": \"<string>\",\n \"destinationTag\": \"<string>\",\n \"addressFrom\": \"<string>\",\n \"signatures\": [\n {\n \"signature\": \"<string>\",\n \"key\": \"<string>\",\n \"swapRequiredMetadata\": {}\n }\n ],\n \"walletInfo\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-partner.houdiniswap.com/v2/exchanges")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"addressTo\": \"<string>\",\n \"quoteId\": \"<string>\",\n \"markup\": 0.5,\n \"refundAddress\": \"<string>\",\n \"refundExtraId\": \"<string>\",\n \"intermediateRefundAddress\": \"<string>\",\n \"destinationTag\": \"<string>\",\n \"addressFrom\": \"<string>\",\n \"signatures\": [\n {\n \"signature\": \"<string>\",\n \"key\": \"<string>\",\n \"swapRequiredMetadata\": {}\n }\n ],\n \"walletInfo\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"created": "2026-09-24T09:33:24.938Z",
"houdiniId": "3HHk9iwihtsUM5tUTksNeR",
"receiverAddress": "8jZnXYnZB1MJQG6zBXyouzcycsaPTrZtPXPHDNifjYaC",
"status": 0,
"anonymous": false,
"expires": "2026-09-24T10:03:24.938Z",
"in": "se",
"inAmount": 0.05,
"inSymbol": "ETH",
"inStatus": 0,
"inCreated": "2026-09-24T09:33:24.938Z",
"outAmount": 1.15832448,
"outSymbol": "SOL",
"eta": 23,
"inAmountUsd": 132.0905,
"isDex": false,
"swapName": "StealthEx",
"depositAddress": "0x0000000000000000000000000000000000000000",
"id": "6ab4ee647d5b21122e18201c",
"hashUrl": "",
"statusLabel": "WAITING",
"inStatusLabel": "NEW",
"displayStatus": "WAITING_FOR_DEPOSIT"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"fields": {},
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}Create exchange
Creates the order from a quoteId.
Quote first, then create right away — a stale quoteId is rejected with 422, and the message names the exact age limit. For fixed: true quotes the locked rate must also still be inside the quote’s validUntil, or you get FIXED_RATE_QUOTE_EXPIRED.
With the order data returned by this endpoint:
- private and standard: send the deposit to
depositAddressbeforeexpires - DEX: see the DEX swap flow
For a private same-token send you can use Create private send order, or stay on this endpoint with a private same-token quoteId — see the Private send guide.
curl --request POST \
--url https://api-partner.houdiniswap.com/v2/exchanges \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"addressTo": "<string>",
"quoteId": "<string>",
"markup": 0.5,
"refundAddress": "<string>",
"refundExtraId": "<string>",
"intermediateRefundAddress": "<string>",
"destinationTag": "<string>",
"addressFrom": "<string>",
"signatures": [
{
"signature": "<string>",
"key": "<string>",
"swapRequiredMetadata": {}
}
],
"walletInfo": "<string>"
}
'import requests
url = "https://api-partner.houdiniswap.com/v2/exchanges"
payload = {
"addressTo": "<string>",
"quoteId": "<string>",
"markup": 0.5,
"refundAddress": "<string>",
"refundExtraId": "<string>",
"intermediateRefundAddress": "<string>",
"destinationTag": "<string>",
"addressFrom": "<string>",
"signatures": [
{
"signature": "<string>",
"key": "<string>",
"swapRequiredMetadata": {}
}
],
"walletInfo": "<string>"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
addressTo: '<string>',
quoteId: '<string>',
markup: 0.5,
refundAddress: '<string>',
refundExtraId: '<string>',
intermediateRefundAddress: '<string>',
destinationTag: '<string>',
addressFrom: '<string>',
signatures: [{signature: '<string>', key: '<string>', swapRequiredMetadata: {}}],
walletInfo: '<string>'
})
};
fetch('https://api-partner.houdiniswap.com/v2/exchanges', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-partner.houdiniswap.com/v2/exchanges",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'addressTo' => '<string>',
'quoteId' => '<string>',
'markup' => 0.5,
'refundAddress' => '<string>',
'refundExtraId' => '<string>',
'intermediateRefundAddress' => '<string>',
'destinationTag' => '<string>',
'addressFrom' => '<string>',
'signatures' => [
[
'signature' => '<string>',
'key' => '<string>',
'swapRequiredMetadata' => [
]
]
],
'walletInfo' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-partner.houdiniswap.com/v2/exchanges"
payload := strings.NewReader("{\n \"addressTo\": \"<string>\",\n \"quoteId\": \"<string>\",\n \"markup\": 0.5,\n \"refundAddress\": \"<string>\",\n \"refundExtraId\": \"<string>\",\n \"intermediateRefundAddress\": \"<string>\",\n \"destinationTag\": \"<string>\",\n \"addressFrom\": \"<string>\",\n \"signatures\": [\n {\n \"signature\": \"<string>\",\n \"key\": \"<string>\",\n \"swapRequiredMetadata\": {}\n }\n ],\n \"walletInfo\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-partner.houdiniswap.com/v2/exchanges")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"addressTo\": \"<string>\",\n \"quoteId\": \"<string>\",\n \"markup\": 0.5,\n \"refundAddress\": \"<string>\",\n \"refundExtraId\": \"<string>\",\n \"intermediateRefundAddress\": \"<string>\",\n \"destinationTag\": \"<string>\",\n \"addressFrom\": \"<string>\",\n \"signatures\": [\n {\n \"signature\": \"<string>\",\n \"key\": \"<string>\",\n \"swapRequiredMetadata\": {}\n }\n ],\n \"walletInfo\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-partner.houdiniswap.com/v2/exchanges")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"addressTo\": \"<string>\",\n \"quoteId\": \"<string>\",\n \"markup\": 0.5,\n \"refundAddress\": \"<string>\",\n \"refundExtraId\": \"<string>\",\n \"intermediateRefundAddress\": \"<string>\",\n \"destinationTag\": \"<string>\",\n \"addressFrom\": \"<string>\",\n \"signatures\": [\n {\n \"signature\": \"<string>\",\n \"key\": \"<string>\",\n \"swapRequiredMetadata\": {}\n }\n ],\n \"walletInfo\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"created": "2026-09-24T09:33:24.938Z",
"houdiniId": "3HHk9iwihtsUM5tUTksNeR",
"receiverAddress": "8jZnXYnZB1MJQG6zBXyouzcycsaPTrZtPXPHDNifjYaC",
"status": 0,
"anonymous": false,
"expires": "2026-09-24T10:03:24.938Z",
"in": "se",
"inAmount": 0.05,
"inSymbol": "ETH",
"inStatus": 0,
"inCreated": "2026-09-24T09:33:24.938Z",
"outAmount": 1.15832448,
"outSymbol": "SOL",
"eta": 23,
"inAmountUsd": 132.0905,
"isDex": false,
"swapName": "StealthEx",
"depositAddress": "0x0000000000000000000000000000000000000000",
"id": "6ab4ee647d5b21122e18201c",
"hashUrl": "",
"statusLabel": "WAITING",
"inStatusLabel": "NEW",
"displayStatus": "WAITING_FOR_DEPOSIT"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"fields": {},
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}{
"message": "<string>",
"code": "<string>",
"requestId": "<string>"
}Authorizations
Send Authorization: <ApiKey>:<ApiSecret> — joined by a colon, raw. No Bearer prefix, no base64.
Body
Request body for creating a new exchange
Destination wallet address where funds will be sent
1 - 200The quoteId from a quote response. It carries the amount, the token pair and the provider, so you do not send those again.
Quotes expire. Create the exchange while the quote is still fresh:
- about a minute for private and standard quotes, and for DEX quotes on a chain's native coin
- 10 minutes for DEX quotes on a token that needs an approval, leaving room for the approval transaction
- once it lapses the call returns 422. Fetch a new quote and retry
If the chosen provider fails, private and standard swaps fall back to the next best route. The order then comes back with rerouted: true and the amounts can differ from the quote, so compare quotedInAmount and quotedOutAmount before showing the result.
Markup for this trade, as a percentage — 0.5 means 0.5%. Range 0–4.
The quote already carries the markup it was priced with, so you normally omit this field.
- send it only to repeat the quote's own markup. A different value is rejected with 422
MARKUP_MISMATCHES_QUOTE - sending it on a DEX quote is rejected with 422
MARKUP_NOT_SUPPORTED_FOR_DEX - above your account ceiling it is rejected with 422
MARKUP_EXCEEDS_LIMIT
To change the markup, quote again with the new value rather than overriding it here.
0 <= x <= 40.5
Where funds are returned if the swap cannot complete.
- use a wallet the user controls, not an exchange deposit address
- must be on the source chain — the refund comes back as the token you deposited
- required when the quote has
fixed: trueorrequiresRefundAddress: true
200Memo/tag for refundAddress, e.g. XRP destination tag, Stellar memo, TON comment. Send it when the source token's chain has memoNeeded: true — see Get chains.
64Where the intermediate hop's funds are returned if that hop cannot complete. Separate from refundAddress, which covers the deposit leg.
- required when the quote has
requiresIntermediateRefundAddress: true; ignored otherwise - must be on the quote's
intermediateRefundChainand able to receiveintermediateRefundToken - use a wallet the user controls, not an exchange deposit address
200Memo/tag for addressTo, e.g. XRP destination tag, Stellar memo, TON comment. Send it when the destination token's chain has memoNeeded: true — see Get chains.
64Source wallet address (required for DEX, ignored for CEX)
EIP-712 signatures for permit-based approvals (DEX only)
Show child attributes
Show child attributes
Name of the wallet the user is swapping from, e.g. MetaMask, Phantom, Trust Wallet. Optional.
256Response
Exchange created
Public order id. Use it with Get order details.
Deposit deadline — send the funds by this time or the order expires. Not a rate lock.
Human-readable state for your UI, derived from status and the leg statuses. Prefer this over status.
WAITING_FOR_DEPOSIT, DEPOSIT_DETECTED, EXCHANGE_IN_PROGRESS, SENDING_TO_INTERMEDIARY, REACHED_INTERMEDIARY, INITIATING_SECOND_EXCHANGE, SECOND_EXCHANGE_IN_PROGRESS, SENDING_TO_RECEIVER, SWAP_COMPLETED, EXPIRED, FAILED, REFUNDED, DELETED Numeric order state. For anything user-facing prefer displayStatus.
-2initializing-1created0waiting for your deposit1confirming your deposit2exchanging3anonymizing — private swaps only4finished5expired6failed7refunded8deleted
-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8 ETA time, depending on swap
Amount to deposit, in the input token.
Symbol of the input token, e.g. BTC.
Amount you receive, in the output token.
Symbol of the output token, e.g. ETH.
USD value of the input amount at order creation time.
outAmount valued in USD.
Token you sent.
Show child attributes
Show child attributes
Token you received.
Show child attributes
Show child attributes
Wallet the payout is sent to.
True when this order took the private two-leg route.
True when the order executed on a DEX route.
True when the order failed but your deposit had already arrived — contact support to recover the funds.
Progress of the incoming leg — your deposit reaching the provider. For reference and debugging; drive your UI from displayStatus instead.
0created1waiting for the deposit2deposit seen, waiting for confirmations3exchanging4sending funds on5finished6failed7refunded8on hold for a compliance check9expired — no deposit arrived in time10finished through a fallback payout
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 When the incoming leg was created.
True when the order settled at a locked rate.
Refund address recorded for this order.
Memo/tag that must be sent with the deposit, on chains that use one (XRP, TON, Stellar, …).
Show it to your user next to depositAddress — a deposit sent without it may not be credited.
True when the deposit can no longer be refunded.
True when the route that executed is not the one that was quoted, because the quoted route failed and a fallback was used. The amounts may differ from the quote — compare inAmount against quotedInAmount and outAmount against quotedOutAmount.
The deposit amount originally quoted, present only when the order was rerouted. inAmount is what the fallback route actually asks for; on an exact-out order this is the side that moves.
The payout originally quoted, present only when the order was rerouted. outAmount is what the fallback route will actually pay; on an exact-in order this is the side that moves.
Hash of the transaction that returned your funds. Absent unless the order refunded.
Chain the refund was returned on, e.g. "bsc". Refunds are returned on the input chain.
Amount returned to you, after the provider's refund fee. Absent when the exchange did not report one.
Extra data from the provider. The shape depends on the route.
Memo/tag required when receiving funds for assets that use one
Name of the exchange handling the deposit leg. Omitted on private orders, which withhold the provider, and on multiswap orders that have not yet been assigned one.
Multi ID. Present only on orders created as part of a batch.
Status of the payout leg. Present only on private orders — public orders settle in a single leg and never carry one. For reference and debugging; drive your UI from displayStatus instead.
0created1waiting for the deposit2deposit seen, waiting for confirmations3exchanging4sending funds on5finished6failed7refunded8on hold for a compliance check9expired — no deposit arrived in time10finished through a fallback payout
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Payout transaction hash of the second leg. Present only on private orders, and only once that leg has paid out. Public orders report their payout hash on inTransactionOutHash instead.
When the order reached a final state. Stamped only when an order finishes — orders that expire, fail or refund never receive one.
The deposit address where the user must send funds. Omitted on multiswap orders whose per-order setup did not complete.