---
title: "Execute a previously quoted swap (pay-per-call)"
method: POST
path: "/api/v1/x402/execute"
tags: ["Swap"]
---

# Execute a previously quoted swap (pay-per-call)

`POST /api/v1/x402/execute`

## Request

- Base URL: `https://app.ravn.exchange`
- URL: `https://app.ravn.exchange/api/v1/x402/execute`
- Auth: none declared

## Request body

- object
  - `quoteToken` string, required: Token returned from POST /api/v1/quote
  - `destinationAddress` string: Late-bound output recipient, honoured by venues that bind the recipient at execution time (Chainflip, Garden, Houdini Swap, Layerswap, Mayan, NEAR Intents, Rift, RAVN two-hop routes). Every other venue fixes the recipient when it prices the quote: supplying a DIFFERENT address here is rejected with INVALID_REQUEST rather than silently ignored, because the payout would otherwise go to the address the quote was priced for while this call reported success. Supply destinationAddress on the quote call instead, and it is honoured by every venue including same-chain ones. A known non-wallet address (a listed token's mint/contract, this swap's own tokens, the native-coin sentinel, burn addresses, Solana core programs, and — via an on-chain lookup that fails open — a Solana token account or program) is rejected with INVALID_REQUEST on either call — funds sent there are unrecoverable. An empty string means not supplied.
  - `refundAddress` string: Late-bound refund recipient, honoured by venues that read one at execution time (Chainflip, Garden, Houdini Swap, NEAR Intents, Rift). Every other venue, Mayan, Layerswap and RAVN two-hop routes included, fixes the refund address when it prices the quote: one that differs from the refund the quote was priced with is rejected with INVALID_REQUEST rather than silently ignored. A quote priced without one refunds where it was priced (normally userAddress) at these venues, so a refundAddress here other than userAddress is rejected too, when userAddress is a valid address on the input chain (selling native Bitcoin with no quoted refund, a venue that can execute uses this one). Supply refundAddress on the quote call instead.
  - `clientTimezone` string: Your end user's IANA timezone, forwarded only to venues that require it (Houdini). Defaults to UTC.

## Response `200`

Execution produced — a DEPOSIT (Bitcoin-source swaps) or a signable TRANSACTION/SIGNATURE (EVM/Solana-source swaps)

- object
  - `data` union: Discriminated on `executionType` — the single field integrators branch on. Mirrors the internal ExecutionType exactly, one of three (per-venue lists are which type that venue returns for a GIVEN quote, not a fixed venue→type map — several venues return different types depending on source chain or execution mode): TRANSACTION — sign & broadcast (Across, Relay, Mayan, Jupiter, Eco, Garden, THORChain, Layerswap from an EVM chain, Bebop self-exec, the same-chain DEX aggregators incl. OKX DEX); with `submit` (Layerswap's memo payment and Jupiter's orders, broadcastViaRavn quotes only — not one carrying RAVN's fee), sign and post it back SIGNATURE — sign typed data, RAVN submits it (0x Gasless, Bebop gasless, CoW, Relay EIP-3009 permit) DEPOSIT — send origin asset to an address (NEAR Intents, Houdini Swap, Rift, Chainflip, Layerswap from Solana, Relay BTC-source, Garden BTC-source)
    - object
      - `executionType` 'TRANSACTION', required
      - `sandbox` true: True when the quote was requested with `sandbox: true`: a test, never send it.
      - `swapId` string: This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote.
      - `quoteToken` string, required: Pass this to submit-signature / status. Compose: post-bind token (hop 1 pays hop 2).
      - `statusRef` string: Poll POST /v1/status with this when present: the venue's own tracking key (compose hop-2 handle, Garden order id, Relay request id). Else omitted — use the origin tx hash (with `submit`, the txHash /v1/submit-transaction returned).
      - `approval` ApprovalDTO: An ERC-20 `approve()` that must be broadcast **and mined** before the swap transaction. Present only when the input token actually needs a fresh allowance — a native-coin sell, or a token the sender (the quote's `userAddress`) already allows the spender enough of, read on-chain at /v1/execute, omits it. When that read fails it is included anyway: send it unless your own read of `spender`'s allowance covers `amount`. The read is a snapshot of the latest block: a swap still pending that spends the same allowance isn't seen, so run one swap per token and spender at a time. Broadcasting the swap before this is mined reverts on `transferFrom`, so integrators must await the receipt, not just the broadcast.
        - `to` string, required: The ERC-20 token contract to send this to.
        - `data` string, required: Encoded `approve(spender, amount)` — send as-is.
        - `value` string, required
        - `chainId` number
        - `spender` string: Who receives the allowance. Read the current allowance for this spender to skip a no-op.
        - `amount` string, required: Minimum allowance this swap needs, in the token's smallest unit.
        - `unlimitedRecommended` boolean: The venue recommends approving more than `amount` once, rather than per swap. True for CoW: its relayer needs an on-chain allowance for every token no matter what, so an exact approval means an approval tx on every single swap — which cancels out the gasless route it is otherwise offering. `data` still encodes the exact `amount`; raise it to an unlimited approval only if your users would rather pay that gas once. Both work.
        - `reset` object: Send this first and wait for it to be mined, then the approval: `approve(spender, 0)`. Only on a token whose approve reverts on a change from one non-zero allowance to another (Ethereum USDT) when the sender holds a partial one.
          - `to` string, required
          - `data` string, required
          - `value` string, required
      - `transaction` object, required
        - `to` string
        - `data` string
        - `value` string
        - `chainId` number
        - `serialized` string: Solana (chainId -2): a base64 versioned transaction for your user's wallet to sign. Sign it exactly as returned — never re-compile it, swap its blockhash or add instructions: some (Mayan) arrive partially signed by one-time keys RAVN generated, and any change voids those signatures. Its blockhash expires ~60–90 s after it was built (for Relay and Mayan, at this /execute response): sign and send within a minute, and past that call /execute again (or re-quote) rather than sending the old one. With `submit`, don't send it: sign it and post it back (see `submit`). Otherwise simulate and send it with commitment `confirmed` (e.g. `sendRawTransaction(tx, { preflightCommitment: 'confirmed' })`): it's built on a confirmed blockhash, and a `finalized` preflight or simulation reports BlockhashNotFound for its first ~13 s. When it carries RAVN's fee (quote `fee`, no execution `fee`), send it from the signing wallet itself: one that runs it inside its own program (a Squads-style smart account) pays the fee where RAVN can't find it, and nothing is owed to you on it.
      - `submit` object: Only on quotes made with `broadcastViaRavn: true`. Don't broadcast this one: sign `transaction.serialized` (a partial signature: it can name a second signer) and POST { quoteToken, signedTransaction } to `url`, which answers with the landed `txHash`. - Layerswap's memo payment: RAVN checks the signed transaction still pays Layerswap the way it matches deposits (its transfer and memo top-level, as some smart-contract wallets don't keep them, and a payment Layerswap can't match is stuck), then sends it. - Jupiter (a two-hop route's first step too): RAVN hands it to Jupiter, which lands it. Its market maker (JupiterZ) or gas sponsor signs some orders as well: a broadcast copy of one of those can't land. Jupiter's market makers hold a price ~20 s: post it within seconds of /v1/execute. The answer comes once it has landed: let the call run up to 2 minutes before timing out. Not on a Jupiter order carrying your fee under Jupiter's 50 bps minimum: RAVN's fee transfer is added to it, which Jupiter's landing doesn't take. Your user alone signs it; broadcast it like any other. Posting the same signed transaction again is safe: it lands once, and a retry answers the landed `txHash`.
        - `url` string, required
      - `fee` DepositFeeDTO: RAVN's integrator fee beside a deposit (or a transaction), in the input token, paid by your user to RAVN; your share is paid out as usual. `deposit.amount` (the transaction) already leaves it out.
        - `amount` string, required: Base units of the input token.
        - `address` string, required: RAVN's fee address on the input chain.
        - `chainId` number, required
        - `transaction` object: EVM: send this transaction too, exactly as given (it carries a tag naming this swap): on its own, or in the same wallet batch or relay as the deposit or swap (EIP-5792, smart account, 7702, 4337) — an ERC-20 fee anywhere; a native-coin fee only beside a NEAR Intents deposit on Ethereum, Base or Polygon (proven from those chains' internal transfers). Any other native-coin fee must be a plain transaction of its own (a contract wallet that can't send one can't pay it). Then pass the on-chain hash of the transaction that carried it (not a batch id or a userOpHash) as `txHash` to getStatus once it has confirmed (simplest: on every poll). One fee-bearing swap per transaction: a batch carrying two fees, or a fee beside another fee-bearing swap, proves only one. Batch a token deposit, or a native-coin deposit to NEAR Intents (it credits coin a smart account moves); send any other native-coin deposit as a plain transfer, as an exchange may not credit coin a contract moved. Absent on Bitcoin: pay `amount` to `address` as an extra output of the SAME transaction that pays the deposit; RAVN finds it there once that transaction confirms. One deposit per Bitcoin transaction: a fee output proves one swap. Absent on Solana too: add the fee to the SAME transaction that pays the deposit exactly `deposit.amount` (SOL as lamports to the deposit wallet, not wrapped) — SOL: a System transfer of `amount` lamports to `address`; a token: `transferChecked` of `amount` to `address`'s associated token account for the input mint (under the mint's token program), after `createAssociatedTokenAccountIdempotent` in case RAVN has none for that token yet (its rent, ~0.0015 SOL once per token, is normally in the quote's network cost). RAVN finds it there once that transaction is finalized, among the deposit address's first and latest transactions; a fee sent in a transaction of its own isn't found. One deposit per Solana transaction: a fee proves one swap. It can't be sent after the deposit, so on a swap the venue refunds it stays with RAVN.
          - `to` string, required
          - `data` string, required
          - `value` string, required
          - `chainId` number, required
      - `calls` CallDTO[]: EVM: everything to send for this swap, in order: `approval.reset`, `approval`, the swap (`transaction`; on a DEPOSIT, the transfer of `deposit.amount` to `deposit.address`), and RAVN's fee (`fee.transaction`). Send each once the one before it has succeeded, and stop if one reverts; or, when `batch` is true, all of them in one atomic wallet batch (EIP-5792 `wallet_sendCalls` with `atomicRequired: true`) — simulate it first, and send them one by one instead if that fails or the last call needs more than ~400k gas (a wallet leaves a batch little gas headroom). The same calls as the fields they come from, nothing more.
        - `to` string, required
        - `data` string, required
        - `value` string, required
        - `chainId` number, required
      - `batch` boolean: Whether `calls` (more than one) may go in one atomic wallet batch (see `calls`).
    - object
      - `executionType` 'SIGNATURE', required
      - `quoteToken` string, required
      - `sandbox` true: True when the quote was requested with `sandbox: true`: a test, never send it.
      - `swapId` string: This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote.
      - `statusRef` string
      - `approval` ApprovalDTO: An ERC-20 `approve()` that must be broadcast **and mined** before the swap transaction. Present only when the input token actually needs a fresh allowance — a native-coin sell, or a token the sender (the quote's `userAddress`) already allows the spender enough of, read on-chain at /v1/execute, omits it. When that read fails it is included anyway: send it unless your own read of `spender`'s allowance covers `amount`. The read is a snapshot of the latest block: a swap still pending that spends the same allowance isn't seen, so run one swap per token and spender at a time. Broadcasting the swap before this is mined reverts on `transferFrom`, so integrators must await the receipt, not just the broadcast.
        - `to` string, required: The ERC-20 token contract to send this to.
        - `data` string, required: Encoded `approve(spender, amount)` — send as-is.
        - `value` string, required
        - `chainId` number
        - `spender` string: Who receives the allowance. Read the current allowance for this spender to skip a no-op.
        - `amount` string, required: Minimum allowance this swap needs, in the token's smallest unit.
        - `unlimitedRecommended` boolean: The venue recommends approving more than `amount` once, rather than per swap. True for CoW: its relayer needs an on-chain allowance for every token no matter what, so an exact approval means an approval tx on every single swap — which cancels out the gasless route it is otherwise offering. `data` still encodes the exact `amount`; raise it to an unlimited approval only if your users would rather pay that gas once. Both work.
        - `reset` object: Send this first and wait for it to be mined, then the approval: `approve(spender, 0)`. Only on a token whose approve reverts on a change from one non-zero allowance to another (Ethereum USDT) when the sender holds a partial one.
          - `to` string, required
          - `data` string, required
          - `value` string, required
      - `typedData` object
      - `approvalData` object
      - `submit` object, required: Where to submit the signed payload back to RAVN.
        - `url` string, required
        - `payload` object
    - object
      - `executionType` 'DEPOSIT', required
      - `quoteToken` string, required
      - `sandbox` true: True when the quote was requested with `sandbox: true`: a test, never send it.
      - `swapId` string: This swap's id in RAVN's ledger: settlement webhooks name it (`swapId`). Absent on a sandbox quote.
      - `deposit` object, required: Send exactly `amount` to `address`. `expiresAt` (ISO 8601) is set when the venue gives the deposit a deadline (Houdini Swap: typically 7-15 minutes): the transfer must have ARRIVED (confirmed on the origin chain) before it, so send it well ahead. A deposit that arrives later lands at an expired order — the venue no longer honours the quoted price and recovering the funds can need its support.
        - `address` string, required
        - `amount` string, required
        - `chainId` number
        - `expiresAt` string
        - `psbt` string: Bitcoin, from a native SegWit (bc1q…) `userAddress`: the deposit as a ready-to-sign PSBT (base64): this output, RAVN's fee as a second output when there is one (`fee`), change back to them, paid from their confirmed coins at mempool.space's half-hour fee rate. Sign and broadcast it as is. Absent when it can't be built (another address type, too little balance): pay `amount` to `address` (and the fee beside it) yourself.
        - `transaction` string: Solana, when the deposit carries RAVN's fee: the deposit and the fee as one unsigned transaction from your user's wallet (`userAddress`), base64 (a token's account at the deposit address and RAVN's opened if missing). Sign and send it within a minute of this response: its blockhash then expires. Past that, pay `amount` and `fee` in one transaction yourself, or call `/execute` again with no (or a new) Idempotency-Key for a fresh one (a new order: send to its deposit, never the old one). A retry with the same key returns this same transaction.
      - `statusRef` string, required: Poll POST /v1/status with this ref — the venue's own tracking key (a deposit address, an order id, or a packed compose handle). Always use this, not `deposit.address`.
      - `fee` DepositFeeDTO: RAVN's integrator fee beside a deposit (or a transaction), in the input token, paid by your user to RAVN; your share is paid out as usual. `deposit.amount` (the transaction) already leaves it out.
        - `amount` string, required: Base units of the input token.
        - `address` string, required: RAVN's fee address on the input chain.
        - `chainId` number, required
        - `transaction` object: EVM: send this transaction too, exactly as given (it carries a tag naming this swap): on its own, or in the same wallet batch or relay as the deposit or swap (EIP-5792, smart account, 7702, 4337) — an ERC-20 fee anywhere; a native-coin fee only beside a NEAR Intents deposit on Ethereum, Base or Polygon (proven from those chains' internal transfers). Any other native-coin fee must be a plain transaction of its own (a contract wallet that can't send one can't pay it). Then pass the on-chain hash of the transaction that carried it (not a batch id or a userOpHash) as `txHash` to getStatus once it has confirmed (simplest: on every poll). One fee-bearing swap per transaction: a batch carrying two fees, or a fee beside another fee-bearing swap, proves only one. Batch a token deposit, or a native-coin deposit to NEAR Intents (it credits coin a smart account moves); send any other native-coin deposit as a plain transfer, as an exchange may not credit coin a contract moved. Absent on Bitcoin: pay `amount` to `address` as an extra output of the SAME transaction that pays the deposit; RAVN finds it there once that transaction confirms. One deposit per Bitcoin transaction: a fee output proves one swap. Absent on Solana too: add the fee to the SAME transaction that pays the deposit exactly `deposit.amount` (SOL as lamports to the deposit wallet, not wrapped) — SOL: a System transfer of `amount` lamports to `address`; a token: `transferChecked` of `amount` to `address`'s associated token account for the input mint (under the mint's token program), after `createAssociatedTokenAccountIdempotent` in case RAVN has none for that token yet (its rent, ~0.0015 SOL once per token, is normally in the quote's network cost). RAVN finds it there once that transaction is finalized, among the deposit address's first and latest transactions; a fee sent in a transaction of its own isn't found. One deposit per Solana transaction: a fee proves one swap. It can't be sent after the deposit, so on a swap the venue refunds it stays with RAVN.
          - `to` string, required
          - `data` string, required
          - `value` string, required
          - `chainId` number, required
      - `calls` CallDTO[]: EVM: everything to send for this swap, in order: `approval.reset`, `approval`, the swap (`transaction`; on a DEPOSIT, the transfer of `deposit.amount` to `deposit.address`), and RAVN's fee (`fee.transaction`). Send each once the one before it has succeeded, and stop if one reverts; or, when `batch` is true, all of them in one atomic wallet batch (EIP-5792 `wallet_sendCalls` with `atomicRequired: true`) — simulate it first, and send them one by one instead if that fails or the last call needs more than ~400k gas (a wallet leaves a batch little gas headroom). The same calls as the fields they come from, nothing more.
        - `to` string, required
        - `data` string, required
        - `value` string, required
        - `chainId` number, required
      - `batch` boolean: Whether `calls` (more than one) may go in one atomic wallet batch (see `calls`).
  - `meta` Meta
    - `requestId` string, required
    - `version` string, required

## Other responses

- `400`: Invalid request, malformed quoteToken, or quote is a preview-only indicative quote (QUOTE_NOT_EXECUTABLE)
- `402`: Payment Required
- `410`: Quote expired
- `502`: BUILD_FAILED: the venue or route couldn't build this swap right now; `message` says why. Re-quote (an alternative may route elsewhere).
- `503`: VENUE_RATE_LIMITED: the venue is rate-limiting RAVN; retry the same call after `error.details.retryAfterSec`.

---

[API](https://skmtc.dev/ravn/apis/ravn-swap-api.md) · [All operations](https://skmtc.dev/ravn/apis/ravn-swap-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ravn/apis/ravn-swap-api/revisions/f1c5a0c63392?raw)
