---
title: "Build one atomic transaction executing every leg of an N:N basket"
method: POST
path: "/multi-quote/build"
---

# Build one atomic transaction executing every leg of an N:N basket

`POST /multi-quote/build`

## Request

- Base URL: `https://swap.vurto.cc/v1`
- URL: `https://swap.vurto.cc/v1/multi-quote/build`
- Auth: none required

No credential is required; wallet is mandatory and declares the EVM address that will sign. Pass quoteId from POST /multi-quote to build that stored basket, or omit quoteId and send chainId/inputLegs/outputLegs to quote and build in one call. steps[] contains one approve step per DISTINCT input token that still needs allowance, followed by ONE transaction step executing every leg atomically. The approve target is the VurtoSwapRouter address (steps[].spender / the response's router), NOT each leg's underlying provider. Never record approve steps through POST /executions. If simulation.status is approval_required, send the approve step(s) first and call this endpoint again. The final transaction is recordable through POST /executions using the quoteId returned by this build.

## Request body

- union
  - object
    - `wallet` string, required: EVM address that will sign the resulting transaction.
    - `quoteId` string, uuid, required: Optional handoff from POST /multi-quote. When present, the intent fields are not required.
    - `chainId` integer
    - `inputLegs` object[]
      - `tokenIn` string, required
      - `amount` string
      - `amountRaw` string
    - `outputLegs` object[]
      - `tokenOut` string, required
      - `outputPercent` integer, required
      - `receiver` string
    - `slippageBps` integer: Applied to every leg. Default 50.
  - object
    - `wallet` string, required: EVM address that will sign the resulting transaction.
    - `quoteId` string, uuid: Optional handoff from POST /multi-quote. When present, the intent fields are not required.
    - `chainId` integer, required
    - `inputLegs` object[], required
      - `tokenIn` string, required
      - `amount` string
      - `amountRaw` string
    - `outputLegs` object[], required
      - `tokenOut` string, required
      - `outputPercent` integer, required
      - `receiver` string
    - `slippageBps` integer: Applied to every leg. Default 50.

## Response `200`

OK

- object
  - `buildId` string, uuid
  - `buildHash` string
  - `quoteId` string, uuid
  - `nextQuoteId` string, uuid: Present when the build refreshed the basket and produced a successor handoff.
  - `wallet` string
  - `chainId` integer
  - `router` string: VurtoSwapRouter address for this chain. Every approve step's spender.
  - `validUntil` integer: Unix ms, 10 minutes out. No refresh endpoint for N:N — rebuild by calling this endpoint again with the same intent.
  - `legs` MultiBuildLeg[]
    - `legIndex` integer
    - `provider` 'velora' | 'kyberswap' | 'uniswap' | 'aerodrome' | 'sushi' | 'pancake' | 'quickswap' | '1inch': CoW never appears here: an off-chain signature or a separately-settled ETH-flow order can't be one leg of a single atomic transaction.
    - `tokenIn` string
    - `tokenOut` string
    - `amountIn` string
    - `minAmountOut` string: Raw minimum this leg's on-chain call enforces, before VurtoSwapRouter's own fee is deducted from what actually reaches the wallet.
    - `receiver` string
    - `netAmountOutEstimate` string
    - `routeSwitch` RouteSwitch: Present only when the route that was built is NOT the route that was chosen. The build falls back to the next credible route when the chosen provider refuses with a request limit, because that refusal is about who is calling, not about the pair or the wallet, and no amount of waiting fixes it. Any other failure still aborts. Switching the route is acceptable, switching it silently is not: a client that shows the route MUST surface this before asking for a signature.
      - `from` string, required: Provider the caller chose.
      - `to` string, required: Provider that actually built the transaction.
      - `reason` string, required: Error code that knocked the chosen route out, today always provider_rate_limited.
  - `simulation` object
    - `status` 'passed' | 'approval_required' | 'incomplete'
    - `gasEstimate` string
    - `reason` string
    - `perLeg` unknown
  - `steps` PlanStep[]
    - `index` integer
    - `type` 'approve' | 'transaction' | 'signature'
    - `humanSummary` string
    - `token` string
    - `spender` string
    - `amount` string
    - `resetFirst` boolean: True when this approve step must zero the allowance before the real approve step runs (some tokens, e.g. USDT, reject a non-zero-to-non-zero approve).
    - `tx` object
      - `to` string
      - `data` string
      - `value` string
    - `gasEstimate` string
    - `scheme` 'eip712'
    - `typedData` object
      - `domain` object
      - `types` object
      - `primaryType` string
      - `message` object
    - `submit` object: Where and how to POST the signature produced from `typedData`.
      - `url` string, uri
      - `method` 'POST'
      - `bodyTemplate` object: Known fields already filled in (chainId as a number, quoteId as a string); fill in `signature` and POST as-is.
  - `refreshedAt` integer

## Other responses

- `400`: invalid_chain_id | invalid_input_legs | invalid_output_legs | too_many_legs | invalid_output_percent | token_required | invalid_amount | amount_required | zero_value_input | no_legs | invalid_receiver
- `401`: missing or invalid credential
- `403`: scope_required — credential lacks the 'build' scope
- `409`: ambiguous_token
- `429`: budget_exhausted
- `502`: no_routes | leg_not_composable | pivot_pricing_failed
- `503`: multi_swap_not_supported_on_chain | multi_swap_pivot_unavailable

---

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