The legacy path/v1/bes/swapsis retired and answers404. Always use/v1/swaps.
Base URL
https://canary-api.sodax.com/v1/swaps.
All amounts on the wire are decimal strings. Unauthenticated; write endpoints such as submit-tx are rate-limited — the exact limit is not published, so treat 429 as the signal.
Endpoint catalog
Tokens
Quote · deadline
Allowance · approve · create intent
Intent lifecycle
Limit orders · gas · fees
Submit-tx state machine (recommended bot path)
Partner fee
Optional on quote / create-intent / allowance / approve / limit-orders. Not onsubmit-tx — by then the fee is already encoded in intent.data.
amount and percentage are present, amount wins. There is no global default fee.
Bot flow: create, submit-tx, poll
Same for same-chain and cross-chain swaps.1. Approve (if needed)
POST /approve returns { tx, resetTx? }.
Some ERC-20s (notably Ethereum USDT of the 2017 TetherToken lineage) reject allowance changes from one non-zero value to another. When resetTx is present:
- Broadcast
resetTxand wait for confirmation. - Then broadcast
tx.
0x000…000) need no approval.
2. Build + broadcast the intent
Use either:POST /intents(HTTP build) and sign/broadcasttxyourself, or@sodax/sdksodax.swaps.createIntent({ … })then continue with HTTP submit-tx.
3. Submit
- Idempotent on
(txHash, srcChainKey)— safe to retry. - Response
data.statusis"inserted"or"duplicate". - Rate-limited → back off on
429. Implement your own backoff:@sodax/swaps-apireplays a429immediately, without one.
4. Poll status
On success, treat these
result fields as the contract for pollers:
Optional wire field: the backend may also return
result.fillTxHash (solver destination-chain fill) when the solver reported a fill. It can be absent even on solved if the fill was confirmed via the on-chain journal instead. The typed TypeScript contract (SubmitTxStatusResultV2 / @sodax/swaps-api) does not include fillTxHash — that client only types dstIntentTxHash, packetData, and intent_hash. For a typed fill hash, use POST /intents/status (numeric status 3 SOLVED → top-level fillTxHash) or the on-chain journal.
On failure, inspect failedAtStep, failureReason, userMessage, intentCancelled, abandonedAt.
Open intent after failed fill: if status is failed and the intent is still open on-chain (intentCancelled not true), surface userMessage and cancel to recover funds. Timed intents can also expire via deadline; limit orders (deadline = 0) never expire — cancel explicitly.
Transient relay blips are retried internally and stay on intermediate statuses — they do not appear asfailed. Treatfailedas real.
5. Optional on-chain journal
Independent of swaps-api self-report (basehttps://api.sodax.com/v1/be):
- Sonic-source create tx:
GET /intent/tx/:txHash - Cross-chain:
GET /intent/:intentHash
404 → open → filled/cancelled. Aggregator lag means this can trail submit-tx/status — soft check only.
Three different status fields
Do not mix these up:
Long-poll: solved packet
POST /intents/packet is a server-side long-poll — one request held until the fill packet lands or timeout elapses (default ~60s). Call once and await; do not spin client-side.
Quote sketch
@sodax/types QuoteRequestV2 shapes — prefer generating clients from the live schema if you are not on TypeScript.
TypeScript clients (optional)
Two packages hit the same HTTP API with different error contracts:SDK adapter (Result<T>)
Standalone client (throws)
Operational checklist
- Source balance ≥
inputAmount+ gas; use a fresh, tip-synced RPC - Approve once (or large allowance) when possible
- Wait for source-chain receipt before
submit-tx - Respect rate limits; retry submit freely (idempotent)
- Poll until
solved/failed; log failure fields - Cross-chain destination funds may lag
solvedby minutes — poll destination balance with a generous timeout if the next step depends on arrival
See also
- Oracle — prices for charts
- Stats — filled-intent volume
- Leverage yield — same submit-tx machine for vaults
- Make a Swap (SDK)