Exact Buy (Exact Out)

View as Markdown

The Swap API supports exact out swaps via the buyAmount parameter. Instead of specifying how much to sell, you specify the exact output the swap must deliver — and 0x sizes and routes the input to guarantee it.

This unlocks use cases like:

  • Merchant checkout where the seller receives exactly the invoiced amount
  • Invoicing and bill pay with no rounding or shortfall
  • Fixed-amount payouts to contractors or sellers regardless of which asset the treasury holds
  • Subscriptions that charge a fixed price each cycle while users hold any asset

Exact out is supported on the Swap API via the buyAmount parameter.


How it works

Send buyAmount on a price or quote request in place of sellAmount. The response sizes the input and returns maxSellAmount — the ceiling the taker needs to cover in balance and allowance.

Taker ──authorizes up to maxSellAmount──► 0x Settler / AllowanceHolder ──sends exactly buyAmount──► Recipient

The swap acquires at least buyAmount and converts any surplus back to the sell token, returning it to the taker in the same transaction. The recipient receives exactly buyAmount.

If recipient is omitted, it defaults to the taker address. Combine buyAmount with recipient to deliver an exact amount directly to a merchant or third-party address — in a single transaction. See Swap and Send for details.


API Reference

buyAmount

PropertyValue
Typestring
RequiredNo (mutually exclusive with sellAmount)
FormatInteger string, in the smallest unit of the buy token (e.g. wei, 6-decimal USDC units)
SupportedSwap API (price, quote)

maxSellAmount

The response field that replaces minBuyAmount when buyAmount is used. It is the maximum amount of sellToken the taker authorizes — the swap will not spend more than this. Any unspent sell token is returned to the taker.

buyAmount and sellAmount are mutually exclusive. Passing both on the same request will result in an error. Use one or the other.

buyAmount is not supported for wrap and unwrap operations (e.g. ETH ↔ WETH). Passing it for those trade types will result in an error. Check for native/wrapped token pairs and use sellAmount for those cases.


Example

A payment provider wants to guarantee a merchant receives exactly 500 USDC. The buyer holds ETH on Base.

1const params = new URLSearchParams({
2 chainId: "8453",
3 sellToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH
4 buyToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
5 buyAmount: "500000000", // exactly 500 USDC (6 decimals)
6 taker: "0xYourTakerAddress",
7 recipient: "0xMerchantAddress", // merchant receives exactly 500 USDC
8});
9
10const response = await fetch(
11 `https://api.0x.org/swap/allowance-holder/quote?${params}`,
12 {
13 headers: {
14 "0x-api-key": process.env.ZEROX_API_KEY!,
15 "0x-version": "v2",
16 },
17 },
18);
19
20const quote = await response.json();
21
22// The taker authorizes up to maxSellAmount
23console.log("Max ETH to spend:", quote.maxSellAmount);
24// The merchant will receive exactly this amount
25console.log("USDC merchant receives:", quote.buyAmount);

Errors

Conflicting amount fields

Passing both buyAmount and sellAmount on the same request is not allowed.

1{
2 "name": "INPUT_INVALID",
3 "message": "The input is invalid",
4 "data": {
5 "details": [
6 {
7 "field": "buyAmount",
8 "reason": "buyAmount and sellAmount are mutually exclusive"
9 }
10 ]
11 }
12}

Wrap/unwrap operations

buyAmount is not supported when sellToken and buyToken are a native/wrapped pair (e.g. ETH ↔ WETH). Use sellAmount for these operations instead.

1{
2 "name": "INPUT_INVALID",
3 "message": "The input is invalid",
4 "data": {
5 "details": [
6 {
7 "field": "buyAmount",
8 "reason": "buyAmount is not supported for wrap/unwrap operations"
9 }
10 ]
11 }
12}

Guard against this by detecting wrap/unwrap pairs before building your request:

1const WRAP_UNWRAP_PAIRS: [string, string][] = [
2 [
3 "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
4 "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
5 ], // ETH ↔ WETH
6 // add other chain pairs as needed
7];
8
9const isWrapUnwrap = (sellToken: string, buyToken: string) =>
10 WRAP_UNWRAP_PAIRS.some(
11 ([a, b]) =>
12 (sellToken.toLowerCase() === a.toLowerCase() &&
13 buyToken.toLowerCase() === b.toLowerCase()) ||
14 (sellToken.toLowerCase() === b.toLowerCase() &&
15 buyToken.toLowerCase() === a.toLowerCase()),
16 );
17
18const params = {
19 sellToken,
20 buyToken,
21 taker,
22 // use sellAmount for wrap/unwrap, buyAmount otherwise
23 ...(isWrapUnwrap(sellToken, buyToken) ? { sellAmount } : { buyAmount }),
24};

Key considerations

  • Taker authorizes a ceiling, not a fixed input. The taker approves up to maxSellAmount. Any unspent sell token is returned in the same transaction — the taker never overpays.
  • The merchant receives exactly buyAmount. This guarantee holds regardless of price movement between quote and settlement.
  • Existing integrations are unaffected. buyAmount is opt-in. Integrations using sellAmount continue to work without changes.
  • Wrap/unwrap is not supported. Check for native/wrapped token pairs and use sellAmount for those cases.
  • All supported chains. Exact Buy is live on every EVM chain the Swap API supports.
  • Pairs naturally with Swap and Send. Use recipient alongside buyAmount to deliver the exact output directly to a merchant in one transaction.