---
title: "Rebuild an existing plan with fresh pricing, without losing slippage protection"
method: POST
path: "/swap/{buildId}/refresh"
---

# Rebuild an existing plan with fresh pricing, without losing slippage protection

`POST /swap/{buildId}/refresh`

## Request

- Base URL: `https://swap.vurto.cc/v1`
- URL: `https://swap.vurto.cc/v1/swap/{buildId}/refresh`
- Auth: none required

Use the `refresh` object from a prior SwapPlan response (or GET/derive `buildId` from `buildId` field) instead of calling POST /swap again with the same quoteId — this endpoint enforces two things POST /swap alone doesn't: the slippage ceiling never loosens across refreshes (`effectiveSlippageCap`), and if the underlying route's final destination address changed (not just repriced — a genuinely different router), it fails closed with 409 route_changed instead of silently swapping you into a different route. When the original provider disappears from the fan-out entirely, the 409 response embeds the current best route ready to use in `error.details.alternative`, so you don't need a third round trip.

## Path parameters

- `buildId` string, uuid, required

## Request body

- object
  - `wallet` string, required: Same EVM address used to build the original plan.
  - `slippageBps` integer: 0-10000. Clamped down to the original build's maxSlippageBps when one was set; never loosened.

## Response `200`

OK, a new SwapPlan with a new buildId

- SwapPlan
  - `buildId` string, uuid: Identity of this specific build. Use in POST /swap/{buildId}/refresh.
  - `buildHash` string: sha256 canonical hash of chainId + wallet + destination + calldata + value. Recompute and compare before signing if you don't trust the transport.
  - `wallet` string
  - `chainId` integer
  - `validUntil` integer: Unix ms. The build expires 10 minutes after creation; call refresh after this.
  - `quote` NormalizedQuote
    - `id` string: Opaque quote id. Pass as `quoteId` to POST /swap or POST /orders.
    - `provider` 'velora' | 'kyberswap' | 'uniswap' | 'aerodrome' | 'sushi' | 'pancake' | 'quickswap' | 'composto' | '1inch' | 'cowswap': Full provider list varies by chain; see GET /chains and provider health.
    - `venues` object[]: The exchanges the route crosses, in order, present only when there is more than one. A `composto` quote carries two entries; a single-venue route omits the field entirely.
      - `provider` string
      - `tokenIn` string
      - `tokenOut` string
    - `providerLabel` string
    - `chainId` integer
    - `tokenIn` string
    - `tokenOut` string
    - `amountIn` string: Raw integer string, in the input token's smallest unit.
    - `amountOut` string: Raw integer string, the provider's expected output.
    - `minimumAmountOut` string: Raw integer string, the worst acceptable output after slippage. This is what the on-chain call enforces.
    - `amountOutUsd` number, nullable
    - `gasUsd` number, nullable
    - `netValueUsd` number, nullable: Output value minus estimated gas minus platform fee. This is the ranking key, not amountOut.
    - `priceImpact` number, nullable
    - `platformFeeBps` integer
    - `platformFeeAmount` string
    - `confidence` integer: 0-100, lower when the provider's own health has degraded recently.
    - `latencyMs` integer
    - `quotedAt` integer: Unix ms.
    - `expiresAt` integer: Unix ms. The provider's own quote TTL, informational — POST /swap always re-quotes fresh before building.
    - `quoteBlock` integer, nullable
    - `mevProtection` 'full' | 'partial' | 'public' | 'unknown'
    - `supportsReceiver` boolean: Whether this provider can deliver the output to a `receiver` different from the wallet that signs.
    - `receiver` string: Present only when the request set a `receiver` different from the signing wallet.
    - `approvalTarget` string, nullable: The spender the approve step of the build will grant the allowance to, so pre-approving this address is safe. It is the same address as `steps[].spender` and `approval.spender` for the same provider. Never absent when an allowance is needed. `null` when there is nothing to approve: native input token, or a CoW order selling the native asset. The Vurto router appears here only when it sits in the call path (a direct pool route where the Vurto fee is charged); on a direct pool route with no fee this is the DEX router itself. If the fee flips between quote and build the build refuses with `approval_target_changed` instead of approving another contract.
    - `route` RouteLeg[]
      - `exchange` string
      - `pool` string
      - `tokenIn` string
      - `tokenOut` string
      - `shareBps` integer: Share of the input routed through this leg, in basis points, for split routes.
    - `badges` string[]: Highlight codes, e.g. best_price, lowest_gas.
    - `warnings` QuoteNotice[]
      - `code` string: Stable code; translate client-side, don't display raw.
      - `vars` object
    - `executionKind` 'transaction' | 'order' | 'onchain_order': Only present on quotes returned from POST /quote. 'transaction': send `steps[].tx`. 'order': sign EIP-712 off-chain, no transaction. 'onchain_order': CoW ETH-flow, a real transaction that only registers the order.
    - `estimatedGas` integer: Gas in UNITS for this route, from our own model, present only when the USD figure also came from it. An integrator ranks in units against the gasPrice it read itself; without this it treats the route as if it cost no gas, which is an advantage by omission.
  - `simulation` object
    - `status` 'passed' | 'approval_required' | 'incomplete'
    - `gasEstimate` string
    - `reason` string
    - `outputCheck` 'passed' | 'skipped'
    - `outputDelta` string
  - `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.
  - `slippageBps` integer: Only present on refresh responses: how many bps the output dropped versus the original build. 0 when the price didn't get worse.
  - `refresh` object
    - `url` string, uri
    - `method` 'POST'
    - `body` object
      - `wallet` string, required: The same signing wallet the plan was built for. The handler requires it: a refresh without it is rejected, and omitting it because the spec did not list it was the exact gap this field closes.
      - `slippageBps` integer, nullable, required
  - `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.

## Other responses

- `400`: invalid_slippage_bps
- `401`: missing or invalid credential
- `404`: build_not_found — wrong wallet, or the 10-minute build window expired
- `409`: route_changed (details.alternative may carry a ready plan) | slippage_exceeded (details has originalAmountOut, freshAmountOut, slippageBps, maxSlippageBps) | quote_expired — the underlying quote handoff (2 min) expired; call POST /swap fresh instead

---

[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)
