> For the complete documentation index, see [llms.txt](https://docs.bitflow.finance/bitflow-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bitflow.finance/bitflow-documentation/developers/hodlmm-api-documentation-v2.md).

# HODLMM API Documentation (v2)

Engine v2 endpoints and sample code for HODLMM retail integrators and market makers.

{% hint style="warning" %}
**Migrate here from v1.** New integrators and market makers should use **engine v2** (`/api/quotes/v2` and `/api/app/v2`). The legacy page remains at HODLMM API Documentation for the stable v1 product.
{% endhint %}

## Migrating from v1

| Do                                                       | Don't                                                         |
| -------------------------------------------------------- | ------------------------------------------------------------- |
| Call **`/api/quotes/v2`** and **`/api/app/v2`** together | Mix v1 app IDs with v2 bins (or the reverse)                  |
| Prefer the contract `pool_id` returned by v2 `/pools`    | Assume adapter short IDs alone are enough across generations  |
| Treat quote state as tip-fresh in-memory SoT             | Expect legacy `X-Allow-Fallback` / DB→chain fallback behavior |

**Why mixing fails:** legacy v1 often uses short pool IDs (`dlmm_3`, `dlmm_10`, …). Engine v2 prefers full contract principals (aliases like `dlmm_1` / `dlmm_10` / `dlmm_3` still work on Quote `/pools` and `/bins`, but app + quotes must stay on the **same** generation). A mixed client can get HTTP 200 with empty bins (`Pool not found`).

**Prod base:** `https://bff.bitflowapis.finance/api`\
**Dev base:** `https://bff.dev.bitflowapis.finance/api`

Quick readiness checks:

```bash
BASE=https://bff.bitflowapis.finance
curl -sS "$BASE/api/quotes/v2/ready"
```

```bash
curl -sS "$BASE/api/quotes/v2/status" | jq '{ready, state_height, chain_tip, tip_lag, pool_count}'
```

```bash
curl -sS "$BASE/api/app/v2/ready"
```

## Market makers (Phase A market + inventory)

Retail LP flows below use Kong **Quote v2** + **App v2**. Market makers should additionally treat:

| Need                          | Surface                                             | Notes                                                                                                     |
| ----------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Market ladder, pool meta, tip | `GET /api/quotes/v2/*` on Kong                      | Live in prod — poll `/status` and prefer `tip_lag == 0`                                                   |
| Owned-bin inventory SoT       | `GET /api/mm/v1/pools/{pool}/positions/{principal}` | **ClusterIP only** (not Kong): `http://bitflow-engine-mm-inventory.core-apps.svc.cluster.local/api/mm/v1` |
| Dapp / analytics positions    | `GET /api/app/v2/users/.../positions/.../bins`      | Display / event-replay — **not** tip-pinned MM inventory                                                  |

**Quote bin domain (do not ask us to change this):**

* List `GET /quotes/v2/bins/{pool}` → **signed** ids (`-500 … 500`)
* Convert at your edge: `unsigned = signed + 500`
* Single-bin `GET /quotes/v2/bins/{pool}/{bin_id}` takes **unsigned**
* List field `liquidity` is always `null` on Quote v2 — size off prices, reserves, and your inventory SoT

**Inventory success body** uses **unsigned** `bin_id` (`0..=1000`) and raw share/reserve strings. Empty book is only `200` + `bins: []` with `clean: true` — never treat `503 TIP_LAG` / `RECONCILE_DIRTY` / `INCOMPLETE` as empty.

```bash
# Market (Kong)
curl -s https://bff.bitflowapis.finance/api/quotes/v2/pools/dlmm_1 \
  | jq '{pool_id, active_bin, bin_step, active, quotable}'
```

```bash
curl -s https://bff.bitflowapis.finance/api/quotes/v2/bins/dlmm_1 \
  | jq '{active_bin_id, bin_count: (.bins|length), first: .bins[0]}'

# Inventory (in-cluster; replace alias + signer)
```

```bash
curl -s "http://bitflow-engine-mm-inventory.core-apps.svc.cluster.local/api/mm/v1/pools/dlmm_1/positions/<signer>" \
  | jq '{pool, pool_alias, clean, tip_lag, bin_id_domain, bins: (.bins|length)}'
```

{% hint style="info" %}
App `.../positions/.../bins` remains fine for charts and retail UX. For withdraw / MM inventory, prefer ClusterIP `/api/mm/v1` (or App `.../current-bins` when that proxy is enabled in your environment).
{% endhint %}

## API Usage and Documents

The Bitflow HODLMM API is available on Kong at `https://bff.bitflowapis.finance`.

**Public access (no API key):**

* Quotes: `/api/quotes/*` (v1) and `/api/quotes/v2/*` (engine)
* App / BFF: `/api/app/*` (v1) and `/api/app/v2/*` (engine)
* Default limit: **50 requests per second** per IP

OpenAPI:

* <https://bff.bitflowapis.finance/api/quotes/docs>
* <https://bff.bitflowapis.finance/api/app/docs>

```javascript
const BFF_API_URL = 'https://bff.bitflowapis.finance/api';

fetch(url, {
  method: 'GET', // or 'POST'
  headers: {
    'Content-Type': 'application/json',
    // Optional: 'X-API-Key': '<bitflow-assigned-api-key>' — only needed for a raised limit
  },
})
```

{% hint style="info" %}
**No API key is required** for the default **50 req/s** tier. Need a higher limit? Ask in **Intercom** and we will issue an `X-API-Key` with a raised quota.
{% endhint %}

{% hint style="info" %}
**Stacks node / Stacks API is not included.** For Stacks RPC and Hiro API access, use [Hiro Platform](https://platform.hiro.so/) and the [Hiro API docs](https://docs.hiro.so/).
{% endhint %}

### Data freshness (engine v2)

Quote **v2** serves from in-memory AMM state at the applied tip — there is no legacy “stale DB → on-chain fallback” path and **`X-Allow-Fallback` does not apply**.

* Prefer `GET /quotes/v2/status` and require `tip_lag == 0` (or your policy max) before size/sign-adjacent work.
* `tip_lag: null` means tip unknown — never treat as caught-up.
* App **v2** analytics can lag (\~event-replay / derived tables). Use `GET /app/v2/ready` and `fresh=true` where supported when you need fresher app reads.

Do **not** treat Kubernetes `/ready` alone as your tip gate for market-making.

### Strict vs Simple Mode

**Strict mode** is ideal for those looking to execute precise liquidity operations. These transactions can be broadcast in Deny mode and use exact bin IDs. If on-chain state changes in a way that affects your quoted bins/amounts, the transaction will revert (for example: if you’re trying to add to the active bin and it shifts by 1 before execution).

**Simple mode** is ideal for those looking to add liquidity with more flexibility and fewer failed transactions. These transactions can be broadcast in Allow mode and use offsets from the active bin ID rather than absolute bin IDs. Transactions are more likely to confirm even when on-chain state changes, since the liquidity operation is applied relative to the current active bin at the time of execution.

## Adding Liquidity

{% stepper %}
{% step %}

### Getting Pool Bins

Before adding liquidity, you need to get the pool's bins. You can do this via the following endpoint:

* `/quotes/v2/bins/{pool_id}`: Returns all bins for a pool

This endpoint is a GET request.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/quotes/v2/bins';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_pool_bins = async (pool_id) => {
  const response = await fetch(BFF_API_URL + `/${pool_id}`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': BFF_API_KEY,
      // Engine v2: no X-Allow-Fallback — use /quotes/v2/status tip_lag instead
    }
  });

  const data = await response.json();
  return data;
};
```

{% hint style="info" %}

### Data freshness

Read more about engine tip / freshness [here](#data-freshness-engine-v2).
{% endhint %}
{% endstep %}

{% step %}

### Getting User Position Bins

After getting pool bins, you need to get the user's position bins. You can do this via the following endpoint:

* `/app/v2/users/{user_address}/positions/{pool_id}/bins`: Returns position bins for a user for a pool

This endpoint is a GET request.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/app/v2/users';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_user_position_bins = async (user_address, pool_id) => {
  const response = await fetch(BFF_API_URL + `/${user_address}/positions/${pool_id}/bins`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': BFF_API_KEY,
      // Engine v2: no X-Allow-Fallback — use /quotes/v2/status tip_lag instead
    },
  });

  const data = await response.json();
  return data;
};
```

{% hint style="info" %}

### Data freshness

Read more about engine tip / freshness [here](#data-freshness-engine-v2).
{% endhint %}
{% endstep %}

{% step %}

### Preparing Bins for Add Liquidity

After getting pool bins and user positions, you need to prepare the bins with the amounts you want to add to each bin.

{% hint style="warning" %}
On Quote **v2**, list bins return `liquidity: null`. Do **not** treat `pool_bin.liquidity` as bin shares for sizing. Use reserves/prices from Quote v2 and owned shares from App positions (retail) or MM inventory (market makers).
{% endhint %}

{% tabs %}
{% tab title="Strict mode" %}
Read more about the different modes [here](#strict-vs-simple-mode).

```javascript
const bins_to_add = [
  {
    bin_id: 500,
    x_amount: 10000000000,
    y_amount: 1750000000
  }
];

const prepare_bins_for_add = (pool_bins, user_positions, active_bin_id, bins_to_add) => {
  const user_position_bins_map = new Map(
    (Array.isArray(user_positions?.bins)
      ? user_positions.bins
      : []).map(bin => [bin.bin_id, bin]));
  
  return bins_to_add.map(add_bin => {
    const pool_bin = pool_bins.bins.find(b => b.bin_id === add_bin.bin_id);
    
    if (!pool_bin) throw new Error(`Bin ${add_bin.bin_id} not found in pool`);
    
    if (add_bin.bin_id < active_bin_id && add_bin.x_amount > 0) {
      throw new Error('Only y_token can be added to bins below the active bin');
    };
    
    if (add_bin.bin_id > active_bin_id && add_bin.y_amount > 0) {
      throw new Error('Only x_token can be added to bins above the active bin');
    };
    
    if (add_bin.bin_id === active_bin_id && add_bin.x_amount === 0 && add_bin.y_amount === 0) {
      throw new Error('Active bin requires at least one token amount to be greater than 0');
    };
    
    return {
      is_active_bin: add_bin.bin_id === active_bin_id,
      bin_id: add_bin.bin_id,
      x_amount: add_bin.x_amount,
      y_amount: add_bin.y_amount,
      bin_price: Number(pool_bin.price),
      reserve_x: Number(pool_bin.reserve_x),
      reserve_y: Number(pool_bin.reserve_y),
      bin_shares: Number(pool_bin.liquidity ?? 0),
      user_liquidity: user_position_bins_map.get(add_bin.bin_id)?.user_liquidity || 0,
      has_ever_added_to_bin: user_position_bins_map.has(add_bin.bin_id)
    };
  });
};
```

{% endtab %}

{% tab title="Simple mode" %}
Read more about the different modes [here](#strict-vs-simple-mode).

```javascript
const bins_to_add_relative = [
  {
    active_bin_offset: 0,
    x_amount: 10000000000,
    y_amount: 1750000000
  }
];

const prepare_bins_for_relative_add = (pool_bins, user_positions, active_bin_id, bins_to_add) => {
  const user_position_bins_map = new Map(
    (Array.isArray(user_positions?.bins)
      ? user_positions.bins
      : []).map(bin => [bin.bin_id, bin]));
  
  return bins_to_add.map(add_bin => {
    const bin_id = active_bin_id + add_bin.active_bin_offset;
    const pool_bin = pool_bins.bins.find(b => b.bin_id === bin_id);
    
    if (!pool_bin) throw new Error(`Bin ${bin_id} not found in pool`);
    
    if (bin_id < active_bin_id && add_bin.x_amount > 0) {
      throw new Error('Only y_token can be added to bins below the active bin');
    };
    
    if (bin_id > active_bin_id && add_bin.y_amount > 0) {
      throw new Error('Only x_token can be added to bins above the active bin');
    };
    
    if (bin_id === active_bin_id && add_bin.x_amount === 0 && add_bin.y_amount === 0) {
      throw new Error('Active bin requires at least one token amount to be greater than 0');
    };
    
    return {
      is_active_bin: bin_id === active_bin_id,
      active_bin_offset: add_bin.active_bin_offset,
      bin_id: bin_id,
      x_amount: add_bin.x_amount,
      y_amount: add_bin.y_amount,
      bin_price: Number(pool_bin.price),
      reserve_x: Number(pool_bin.reserve_x),
      reserve_y: Number(pool_bin.reserve_y),
      bin_shares: Number(pool_bin.liquidity ?? 0),
      user_liquidity: user_position_bins_map.get(bin_id)?.user_liquidity || 0,
      has_ever_added_to_bin: user_position_bins_map.has(bin_id)
    };
  });
};
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Executing Add Liquidity

After preparing the bins to add, you can execute the add by calling the liquidity router contract with the necessary parameters.

{% hint style="info" %}
Contract-call construction (Strict / Simple mode) is unchanged from v1. Use the matching **Executing** section on HODLMM API Documentation, and point any `get_token_asset_name` fetch at **`/quotes/v2/tokens`** instead of `/quotes/v1/tokens`.
{% endhint %}
{% endstep %}
{% endstepper %}

***

## Withdrawing Liquidity

{% stepper %}
{% step %}

### Getting User Position Bins

Before withdrawing liquidity, you need to get the user's position bins. You can do this via the following endpoint:

* `/app/v2/users/{user_address}/positions/{pool_id}/bins`: Returns position bins for a user for a pool

This endpoint is a GET request.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/app/v2/users';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_user_position_bins = async (user_address, pool_id) => {
  const response = await fetch(BFF_API_URL + `/${user_address}/positions/${pool_id}/bins`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': BFF_API_KEY,
      // Engine v2: no X-Allow-Fallback — use /quotes/v2/status tip_lag instead
    },
  });

  const data = await response.json();
  return data;
};
```

{% hint style="info" %}

### Data freshness

Read more about engine tip / freshness [here](#data-freshness-engine-v2).
{% endhint %}
{% endstep %}

{% step %}

### Preparing Bins for Withdraw Liquidity

After getting user positions, you need to prepare the bins with the percentage you want to withdraw from each bin.

{% tabs %}
{% tab title="Strict mode" %}
Read more about the different modes [here](#strict-vs-simple-mode).

```javascript
const prepare_bins_for_withdraw = (user_positions, withdrawal_percentage) => {
  const user_position_bins_map = new Map(
    (Array.isArray(user_positions?.bins)
      ? user_positions.bins
      : []).map(bin => [bin.bin_id, bin]));

  return user_positions.bins
    .filter(bin => bin.user_liquidity > 0)
    .map(bin => ({
      bin_id: bin.bin_id,
      user_liquidity: bin.user_liquidity,
      liquidity: bin.liquidity,
      reserve_x: bin.reserve_x,
      reserve_y: bin.reserve_y,
      withdrawal_percentage,
      has_ever_added_to_bin: user_position_bins_map.has(bin.bin_id)
    }));
};
```

{% endtab %}

{% tab title="Simple mode" %}
Read more about the different modes [here](#strict-vs-simple-mode).

```javascript
const prepare_bins_for_relative_withdraw = (user_positions, active_bin_id, withdrawal_percentage) => {
  const user_position_bins_map = new Map(
    (Array.isArray(user_positions?.bins)
      ? user_positions.bins
      : []).map(bin => [bin.bin_id, bin]));

  return user_positions.bins
    .filter(bin => bin.user_liquidity > 0)
    .map(bin => ({
      active_bin_offset: bin.bin_id - active_bin_id,
      bin_id: bin.bin_id,
      user_liquidity: bin.user_liquidity,
      liquidity: bin.liquidity,
      reserve_x: bin.reserve_x,
      reserve_y: bin.reserve_y,
      withdrawal_percentage,
      has_ever_added_to_bin: user_position_bins_map.has(bin.bin_id)
    }));
};
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Executing Withdraw Liquidity

After preparing the bins to withdraw, you can execute the withdrawal by calling the liquidity router contract with the necessary parameters.

{% hint style="info" %}
Contract-call construction (Strict / Simple mode) is unchanged from v1. Use the matching **Executing** section on HODLMM API Documentation, and point any `get_token_asset_name` fetch at **`/quotes/v2/tokens`** instead of `/quotes/v1/tokens`.
{% endhint %}
{% endstep %}
{% endstepper %}

***

## Moving Liquidity

{% stepper %}
{% step %}

### Getting Pool Bins

Before moving liquidity, you need to get the pool's bins. You can do this via the following endpoint:

* `/quotes/v2/bins/{pool_id}`: Returns all bins for a pool

This endpoint is a GET request.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/quotes/v2/bins';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_pool_bins = async (pool_id) => {
  const response = await fetch(BFF_API_URL + `/${pool_id}`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': BFF_API_KEY,
      // Engine v2: no X-Allow-Fallback — use /quotes/v2/status tip_lag instead
    },
  });

  const data = await response.json();
  return data;
};
```

{% hint style="info" %}

### Data freshness

Read more about engine tip / freshness [here](#data-freshness-engine-v2).
{% endhint %}
{% endstep %}

{% step %}

### Getting User Position Bins

After getting pool bins, you need to get the user's position bins. You can do this via the following endpoint:

* `/app/v2/users/{user_address}/positions/{pool_id}/bins`: Returns position bins for a user for a pool

This endpoint is a GET request.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/app/v2/users';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_user_position_bins = async (user_address, pool_id) => {
  const response = await fetch(BFF_API_URL + `/${user_address}/positions/${pool_id}/bins`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': BFF_API_KEY,
      // Engine v2: no X-Allow-Fallback — use /quotes/v2/status tip_lag instead
    },
  });

  const data = await response.json();
  return data;
};
```

{% hint style="info" %}

### Data freshness

Read more about engine tip / freshness [here](#data-freshness-engine-v2).
{% endhint %}
{% endstep %}

{% step %}

### Preparing Bins for Move Liquidity

After getting pool bins and user positions, you need to prepare the bins you want to move liquidity from, to, and the amount of liquidity to move.

{% tabs %}
{% tab title="Strict mode" %}
Read more about the different modes [here](#strict-vs-simple-mode).

```javascript
const bins_to_move = [
  {
    from_bin_id: 480,
    to_bin_id: 498,
    amount: 12848399
  }
];

const prepare_bins_for_move = (pool_bins, user_positions, active_bin_id, bins_to_move) => {
  const user_position_bins_map = new Map(
    (Array.isArray(user_positions?.bins)
      ? user_positions.bins
      : []).map(bin => [bin.bin_id, bin]));
  
  return bins_to_move.map(move => {
    const { from_bin_id, to_bin_id, amount } = move;
    
    const from_bin = user_positions.bins.find(b => b.bin_id === from_bin_id);
    if (!from_bin) throw new Error('Unable to find from_bin in user positions');
    if (from_bin.user_liquidity === 0) throw new Error('User has no liquidity in from_bin');
    if (amount > from_bin.user_liquidity) throw new Error('Amount exceeds user liquidity in from_bin');
  
    const to_bin = pool_bins.bins.find(b => b.bin_id === to_bin_id);
    if (!to_bin) throw new Error('Unable to find to_bin in pool bins');

    if (from_bin_id === to_bin_id) throw new Error('Cannot move liquidity to the same bin');
    
    const x_amount = Math.floor((amount * from_bin.reserve_x) / from_bin.liquidity);
    const y_amount = Math.floor((amount * from_bin.reserve_y) / from_bin.liquidity);

    if (to_bin_id < active_bin_id && x_amount > 0) {
      throw new Error('Only y_token can be added to bins below the active bin');
    };
    
    if (to_bin_id > active_bin_id && y_amount > 0) {
      throw new Error('Only x_token can be added to bins above the active bin');
    };
    
    if (to_bin_id === active_bin_id && x_amount === 0 && y_amount === 0) {
      throw new Error('Active bin requires at least one token amount to be greater than 0');
    };
    
    return {
      from_bin_id,
      to_bin_id,
      amount,
      x_amount,
      y_amount,
      bin_price: Number(to_bin.price),
      reserve_x: Number(to_bin.reserve_x),
      reserve_y: Number(to_bin.reserve_y),
      bin_shares: Number(to_bin.liquidity ?? 0),
      is_active_bin: to_bin_id === active_bin_id,
      has_ever_added_to_from_bin: user_position_bins_map.has(from_bin_id),
      has_ever_added_to_to_bin: user_position_bins_map.has(to_bin_id)
    };
  });
};
```

{% endtab %}

{% tab title="Simple mode" %}
Read more about the different modes [here](#strict-vs-simple-mode).

```javascript
const bins_to_move_relative = [
  {
    from_bin_id: 480,
    active_bin_offset: -2,
    amount: 12848399
  }
];

const prepare_bins_for_relative_move = (pool_bins, user_positions, active_bin_id, bins_to_move) => {
  const user_position_bins_map = new Map(
    (Array.isArray(user_positions?.bins)
      ? user_positions.bins
      : []).map(bin => [bin.bin_id, bin]));
  
  return bins_to_move.map(move => {
    const { from_bin_id, active_bin_offset, amount } = move;
    const to_bin_id = active_bin_id + active_bin_offset;
    
    const from_bin = user_positions.bins.find(b => b.bin_id === from_bin_id);
    if (!from_bin) throw new Error('Unable to find from_bin in user positions');
    if (from_bin.user_liquidity === 0) throw new Error('User has no liquidity in from_bin');
    if (amount > from_bin.user_liquidity) throw new Error('Amount exceeds user liquidity in from_bin');
  
    const to_bin = pool_bins.bins.find(b => b.bin_id === to_bin_id);
    if (!to_bin) throw new Error('Unable to find to_bin in pool bins');

    if (from_bin_id === to_bin_id) throw new Error('Cannot move liquidity to the same bin');
    
    const x_amount = Math.floor((amount * from_bin.reserve_x) / from_bin.liquidity);
    const y_amount = Math.floor((amount * from_bin.reserve_y) / from_bin.liquidity);

    if (to_bin_id < active_bin_id && x_amount > 0) {
      throw new Error('Only y_token can be added to bins below the active bin');
    };
    
    if (to_bin_id > active_bin_id && y_amount > 0) {
      throw new Error('Only x_token can be added to bins above the active bin');
    };
    
    if (to_bin_id === active_bin_id && x_amount === 0 && y_amount === 0) {
      throw new Error('Active bin requires at least one token amount to be greater than 0');
    };
    
    return {
      from_bin_id,
      active_bin_offset,
      to_bin_id,
      amount,
      x_amount,
      y_amount,
      bin_price: Number(to_bin.price),
      reserve_x: Number(to_bin.reserve_x),
      reserve_y: Number(to_bin.reserve_y),
      bin_shares: Number(to_bin.liquidity ?? 0),
      is_active_bin: to_bin_id === active_bin_id,
      has_ever_added_to_from_bin: user_position_bins_map.has(from_bin_id),
      has_ever_added_to_to_bin: user_position_bins_map.has(to_bin_id)
    };
  });
};
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Executing Move Liquidity

After preparing the bins to move, you can execute the move by calling the liquidity router contract with the necessary parameters.

{% hint style="info" %}
Contract-call construction (Strict / Simple mode) is unchanged from v1. Use the matching **Executing** section on HODLMM API Documentation, and point any `get_token_asset_name` fetch at **`/quotes/v2/tokens`** instead of `/quotes/v1/tokens`.
{% endhint %}
{% endstep %}
{% endstepper %}

***

## Swapping Tokens

{% stepper %}
{% step %}

### Getting a Quote

Before executing a swap, you need to get a quote. You can do this via the following endpoints:

* `/quotes/v2/quote`: Returns the best route
* `/quotes/v2/quote/multi`: Returns all routes

These endpoints are POST requests and the request body is the same for both.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/quotes/v2/quote/multi';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_quote = async (input_token, output_token, amount_in, amm_strategy = 'best', slippage_tolerance = 1) => {
  const response = await fetch(BFF_API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': BFF_API_KEY },
    body: JSON.stringify({
      input_token, // Input token contract address
      output_token, // Output token contract address
      amount_in, // Input token amount (in uints)
      amm_strategy, // AMM strategy to use (optional, default: best)
      slippage_tolerance // Slippage tolerance (optional, 1 = 1%)
    })
  });

  const data = await response.json();
  return data;
};
```

{% endstep %}

{% step %}

### Getting Swap Parameters

After getting a quote, you need to generate the swap parameters. You can do this via the following endpoint:

* `/quotes/v2/swap`: Returns the swap parameters for a route

This endpoint is a POST request.

```javascript
const BFF_API_URL = 'https://<api-endpoint>/quotes/v2/swap';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_swap_params = async (amount_in, input_token, output_token, route) => {
  const response = await fetch(BFF_API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': BFF_API_KEY },
    body: JSON.stringify({
      execution_path: route.execution_path,
      amount_in,
      amount_out: route.amount_out,
      input_token,
      output_token,
      input_token_decimals: route.input_token_decimals,
      output_token_decimals: route.output_token_decimals,
      slippage_tolerance: route.slippage_tolerance,
      swap_parameters_type: 'simple' // Swap parameters type (optional, default: 'simple', 'simple' or 'full')
    })
  });

  const data = await response.json();
  return data;
};
```

{% endstep %}

{% step %}

### Executing a Swap

After getting the swap parameters, you can execute the swap by calling the swap router contract with the necessary parameters.

{% hint style="info" %}
Swap broadcast helpers are unchanged from v1. Use **Executing a Swap** on HODLMM API Documentation.
{% endhint %}
{% endstep %}
{% endstepper %}

***

## Getting Data

| Method | Path                                            | Notes                                                  |
| ------ | ----------------------------------------------- | ------------------------------------------------------ |
| GET    | `/quotes/v2/tokens`                             | Available tokens                                       |
| GET    | `/quotes/v2/pools`                              | Available pools (prefer contract `pool_id`)            |
| GET    | `/quotes/v2/pools/{pool_id}`                    | One pool (alias or contract)                           |
| GET    | `/quotes/v2/pairs`                              | Trading pairs                                          |
| GET    | `/quotes/v2/bins/{pool_id}`                     | Ladder — **signed** bin ids; `liquidity` always `null` |
| GET    | `/app/v2/pools/{pool_id}`                       | App pool object (analytics)                            |
| GET    | `/app/v2/pools/{pool_id}/bin-price-history`     | Price history                                          |
| GET    | `/app/v2/users/{user}/liquidity/{pool_id}`      | User position summary                                  |
| GET    | `/app/v2/users/{user}/positions/{pool_id}/bins` | Position bins (display / event-replay)                 |
| GET    | `/quotes/v2/status`                             | Tip / readiness snapshot for market makers             |
| GET    | `/quotes/v2/ready` / `/app/v2/ready`            | Warmth gates                                           |

```javascript
const BFF_API_URL = 'https://<api-endpoint>/quotes/v2/pools';
const BFF_API_KEY = '<bitflow-assigned-api-key>';

const get_pool_data = async (pool_id) => {
  const response = await fetch(BFF_API_URL + `/${pool_id}`, {
    method: 'GET',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': BFF_API_KEY }
  });
  return response.json();
};
```

{% hint style="info" %}
For tip-accurate owned bins (MM / withdraw SoT), use ClusterIP `/api/mm/v1/pools/{pool}/positions/{principal}` — see [Market makers](#market-makers-phase-a-market--inventory).
{% endhint %}
