> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitebit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Market Maker Integration Guide

> Guide for professional market makers — Market-Making Program terms, colocation setup, available endpoints, quoting strategy, and safety mechanisms.

export const endpointLimits = [{
  endpoint: "GET /api/v4/public/markets",
  method: "GET",
  limit: 2000
}, {
  endpoint: "GET /api/v4/public/ticker",
  method: "GET",
  limit: 2000
}, {
  endpoint: "GET /api/v4/public/orderbook/{market}",
  method: "GET",
  limit: 600
}, {
  endpoint: "POST /api/v4/trade-account/balance",
  method: "POST",
  limit: 12000
}, {
  endpoint: "POST /api/v4/order/new",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/order/market",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/order/bulk",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/order/cancel",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/order/modify",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/order/kill-switch",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/collateral-account/balance",
  method: "POST",
  limit: 12000
}, {
  endpoint: "POST /api/v4/order/collateral/limit",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/order/collateral/bulk",
  method: "POST",
  limit: 10000
}, {
  endpoint: "POST /api/v4/main-account/history",
  method: "POST",
  limit: 200
}];

export const ConceptTable = ({title, columns, rows, codeColumns = []}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const T = isDark ? {
    border: '#374151',
    borderSubtle: '#1f2937',
    headerBg: '#1f2937',
    headerText: '#9ca3af',
    titleBg: '#1f2937',
    titleText: '#d1d5db',
    codeBg: '#374151',
    codeText: '#e5e7eb',
    cellText: '#d1d5db'
  } : {
    border: '#e5e7eb',
    borderSubtle: '#f3f4f6',
    headerBg: '#f9fafb',
    headerText: '#6b7280',
    titleBg: '#f9fafb',
    titleText: '#374151',
    codeBg: '#f3f4f6',
    codeText: '#1f2937',
    cellText: '#374151'
  };
  const MONO = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
  const codeSet = new Set(codeColumns);
  const gridTemplateColumns = columns.map(col => col.width || '1fr').join(' ');
  const HEADER = {
    padding: '0.5rem 1rem',
    fontSize: '0.6875rem',
    fontWeight: 600,
    textTransform: 'uppercase',
    letterSpacing: '0.05em',
    whiteSpace: 'nowrap',
    color: T.headerText,
    backgroundColor: T.headerBg,
    borderBottom: `1px solid ${T.border}`
  };
  const CELL = {
    padding: '0.625rem 1rem',
    fontSize: '0.8125rem',
    display: 'flex',
    alignItems: 'center',
    minWidth: 0,
    color: T.cellText
  };
  return <div style={{
    margin: '1.25rem 0',
    borderRadius: '0.5rem',
    border: `1px solid ${T.border}`,
    overflow: 'hidden',
    fontSize: '0.8125rem'
  }}>
      {title && <div style={{
    padding: '0.5rem 1rem',
    fontSize: '0.75rem',
    fontWeight: 600,
    letterSpacing: '0.02em',
    backgroundColor: T.titleBg,
    borderBottom: `1px solid ${T.border}`,
    color: T.titleText
  }}>
          {title}
        </div>}
      <div style={{
    display: 'grid',
    gridTemplateColumns,
    width: '100%',
    overflowX: 'auto'
  }}>

        {columns.map(col => <div key={col.key} style={HEADER}>{col.header}</div>)}

        {rows.map((row, i) => {
    const borderTop = `1px solid ${i === 0 ? T.border : T.borderSubtle}`;
    return columns.map(col => {
      const value = row[col.key];
      const isCode = codeSet.has(col.key);
      const display = value != null ? String(value) : '—';
      return <div key={`${i}-${col.key}`} style={{
        ...CELL,
        borderTop
      }}>
                {isCode ? <span style={{
        padding: '0.125rem 0.375rem',
        borderRadius: '0.25rem',
        fontSize: '0.75rem',
        fontFamily: MONO,
        backgroundColor: T.codeBg,
        color: T.codeText,
        whiteSpace: 'nowrap'
      }}>
                    {display}
                  </span> : display}
              </div>;
    });
  })}

      </div>
    </div>;
};

export const RegionBaseUrl = ({className = "", showBaseUrl = true, protocol = "https"}) => {
  const {useState, useEffect, useRef} = React;
  const EU_ENABLED = false;
  const [region, setRegionState] = useState(() => {
    if (!EU_ENABLED) return "com";
    if (typeof window !== 'undefined') {
      return localStorage.getItem("api-region-preference") || "com";
    }
    return "com";
  });
  const [mounted, setMounted] = useState(false);
  const observerRef = useRef(null);
  const isSyncingRef = useRef(false);
  const updateAllContentOnPage = targetRegion => {
    try {
      const domainFrom = targetRegion === "eu" ? "whitebit.com" : "whitebit.eu";
      const domainTo = targetRegion === "eu" ? "whitebit.eu" : "whitebit.com";
      const links = document.querySelectorAll('a');
      links.forEach(link => {
        let href = link.getAttribute('href');
        if (href && href.includes(domainFrom)) {
          link.setAttribute('href', href.replace(domainFrom, domainTo));
        }
      });
      const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        acceptNode: node => {
          if (node.parentElement?.closest('.region-toggle-component')) {
            return NodeFilter.FILTER_REJECT;
          }
          return node.textContent.includes(domainFrom) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      });
      let currentNode;
      while (currentNode = walker.nextNode()) {
        currentNode.textContent = currentNode.textContent.replace(new RegExp(domainFrom, 'g'), domainTo);
      }
      console.log(`[RegionSync] Global content updated to ${domainTo}`);
    } catch (e) {
      console.error("[RegionSync] Error updating content:", e);
    }
  };
  const updateRegion = (newRegion, source) => {
    if (region === newRegion) return;
    console.log(`[RegionBaseUrl] Updating to "${newRegion}" (Source: ${source})`);
    if (source === 'observer') {
      isSyncingRef.current = true;
      setTimeout(() => isSyncingRef.current = false, 1000);
    }
    setRegionState(newRegion);
    localStorage.setItem("api-region-preference", newRegion);
    updateAllContentOnPage(newRegion);
    if (source === 'user-click') {
      window.dispatchEvent(new CustomEvent("regionChange", {
        detail: newRegion
      }));
      attemptToUpdateNativeDropdown(newRegion, 0);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 1), 500);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 2), 1500);
    }
  };
  const attemptToUpdateNativeDropdown = (targetRegion, attempt) => {
    if (isSyncingRef.current) return;
    try {
      const targetUrl = targetRegion === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
      const targetDesc = targetRegion === "eu" ? "EU Server" : "Production Server";
      const selects = document.querySelectorAll('select');
      for (const select of selects) {
        if (select.innerHTML.includes('whitebit.com') || select.innerHTML.includes('whitebit.eu')) {
          select.value = targetUrl;
          select.dispatchEvent(new Event('change', {
            bubbles: true
          }));
          return;
        }
      }
      const buttons = Array.from(document.querySelectorAll('button, [role="combobox"]'));
      const serverSelector = buttons.find(btn => {
        if (btn.closest('a') || btn.closest('[class*="card"]') || btn.closest('nav')) {
          return false;
        }
        const txt = btn.textContent || "";
        const isServerDropdown = (txt.includes('Production Server') || txt.includes('EU Server') || txt.includes('WhiteBIT Global Server') || txt.includes('WhiteBIT EU Server')) && !txt.includes('Run') && !txt.includes('Send') || btn.getAttribute('role') === 'combobox';
        return isServerDropdown;
      });
      if (serverSelector) {
        const currentText = serverSelector.textContent || "";
        if (currentText.includes(targetDesc)) return;
        serverSelector.click();
        setTimeout(() => {
          const options = document.querySelectorAll('[role="option"], li, button');
          for (const opt of options) {
            const optText = opt.textContent || "";
            if (optText.includes(targetDesc) || optText.includes(targetUrl)) {
              opt.click();
              return;
            }
          }
        }, 100);
      }
    } catch (e) {
      console.error("[Sync] Error:", e);
    }
  };
  useEffect(() => {
    if (!EU_ENABLED) return;
    setMounted(true);
    updateAllContentOnPage(region);
    const handleStorageChange = e => {
      if (e.key === "api-region-preference" && e.newValue) {
        updateRegion(e.newValue, 'storage');
      }
    };
    const handleRegionChange = e => {
      if (e.detail !== region) {
        updateRegion(e.detail, 'event');
      }
    };
    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("regionChange", handleRegionChange);
    observerRef.current = new MutationObserver(mutations => {
      if (isSyncingRef.current) return;
      updateAllContentOnPage(region);
      for (const mutation of mutations) {
        if (mutation.type !== 'childList' && mutation.type !== 'characterData') continue;
        const target = mutation.target;
        const el = target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
        if (el && (el.getAttribute('role') === 'option' || el.closest('[role="listbox"]'))) continue;
        const text = target.textContent || "";
        if (text.includes('WhiteBIT EU Server') || text.includes('https://whitebit.eu') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'eu') updateRegion('eu', 'observer');
          }
        } else if (text.includes('WhiteBIT Global Server') || text.includes('https://whitebit.com') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'com') updateRegion('com', 'observer');
          }
        }
      }
    });
    observerRef.current.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true
    });
    if (typeof window !== 'undefined') {
      const current = localStorage.getItem("api-region-preference");
      if (current) attemptToUpdateNativeDropdown(current, 'init');
    }
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("regionChange", handleRegionChange);
      if (observerRef.current) observerRef.current.disconnect();
    };
  }, [region]);
  const apiBaseUrl = protocol === "wss" ? region === "eu" ? "wss://api.whitebit.eu/ws" : "wss://api.whitebit.com/ws" : region === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
  const baseUrlLabel = protocol === "wss" ? "WebSocket URL" : "Base URL";
  if (!EU_ENABLED) {
    if (!showBaseUrl) return null;
    return <div className={`flex items-center gap-2 flex-wrap my-4 region-toggle-component ${className}`}>
                <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">
                    {baseUrlLabel}
                </span>
                <span className="text-sm text-gray-400">:</span>
                {protocol === "wss" ? <code className="text-sm font-mono text-primary dark:text-primary-light">
                        {apiBaseUrl}
                    </code> : <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                        {apiBaseUrl}
                    </a>}
            </div>;
  }
  if (!mounted) return null;
  return <div className={`flex items-center gap-2 flex-wrap my-4 region-toggle-component ${className}`}>
            <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">
                {baseUrlLabel}
            </span>
            <span className="text-sm text-gray-400">(</span>
            <div className="inline-flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5 border border-gray-200 dark:border-gray-700">
                <button onClick={() => updateRegion("com", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "com" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .com
                </button>
                <button onClick={() => updateRegion("eu", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "eu" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .eu
                </button>
            </div>
            <span className="text-sm text-gray-400">)</span>
            {showBaseUrl && <>
                    <span className="text-sm text-gray-400">:</span>
                    {protocol === "wss" ? <code className="text-sm font-mono text-primary dark:text-primary-light">
                            {apiBaseUrl}
                        </code> : <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                            {apiBaseUrl}
                        </a>}
                </>}
        </div>;
};

<RegionBaseUrl />

The Market-Making Program provides dedicated infrastructure for professional liquidity providers: maker-fee rebates, [colocation](/platform/colocation), and a 35-endpoint API subset reachable from inside the matching-engine zone.

## Market-Making Program overview

Maker-fee rebates are tiered by 30-day rolling trading volume, with separate tiers for spot and futures. Negative maker fees are rebates — the exchange pays the market maker for each maker order filled.

Rates and volume breakpoints change over time and are not duplicated here. For current fees and tier requirements, see the [Market-Making Program page](https://institutional.whitebit.com/market-making-program), the [trading fees page](https://whitebit.com/trading-data/trading-fees), or the [VIP program](https://whitebit.com/vip-program).

**Program benefits beyond fees:**

* [Colocation](/platform/colocation) access
* Dedicated account manager
* Cross-marketing support
* [Sub-accounts](/products/sub-accounts/overview) for strategy separation
* Access to [Liquidity Provision program](https://whitebit.com/m/liquidity-provision) for non-MM liquidity partnerships

## Prerequisites

* **Approved Market-Making Program enrollment** — contact `institutional@whitebit.com` with trading-volume history and target markets (see [How to apply](#how-to-apply)); the [Market-Making overview](/guides/market-maker-overview) covers program terms.
* **Colocation connection details** — provisioned by the account manager for latency-sensitive strategies (see [Colocation setup](#colocation-setup)).
* **API access** — [HMAC-signed requests](/api-reference/authentication).

## Colocation setup

Colocation provides low-latency API access from AWS infrastructure co-located with the WhiteBIT matching engine.

The account manager provides the specific AWS region, availability zone, and connection endpoints during onboarding.

**EC2 sizing recommendations:**

* Network: up to **10Gbit bandwidth**
* CPU: minimum **4 vCPU cores**
* Low-performance VPS instances result in higher latency

Both REST API and WebSocket connections are available via colocation.

Contact the designated account manager for connection details including base URLs and availability zone placement.

<Warning>
  Colocation endpoints are a SUBSET of the full WhiteBIT API. Only the 35 endpoints listed below are available via colocation infrastructure. All other API endpoints must be accessed through the standard public API.
</Warning>

See the [Colocation](/platform/colocation) page for additional infrastructure details.

## Available colocation endpoints

The colocation infrastructure exposes 35 endpoints organized into three categories: spot trading (17), collateral trading for both Margin and Futures (14), and utility (4).

### Spot trading (17 endpoints)

| Endpoint                                      | Description                                                 |
| --------------------------------------------- | ----------------------------------------------------------- |
| `POST /api/v4/trade-account/balance`          | Trade account balance                                       |
| `POST /api/v4/trade-account/executed-history` | Executed order history                                      |
| `POST /api/v4/trade-account/order`            | Single order details                                        |
| `POST /api/v4/trade-account/order/history`    | Order history                                               |
| `POST /api/v4/orders`                         | Active orders list                                          |
| `POST /api/v4/order/cancel`                   | Cancel an order                                             |
| `POST /api/v4/order/cancel/bulk`              | Cancel up to 100 orders in one request                      |
| `POST /api/v4/order/cancel/all`               | Cancel all open orders (optional `market` and `type` scope) |
| `POST /api/v4/order/new`                      | Place a limit order                                         |
| `POST /api/v4/order/bulk`                     | Place up to 20 limit orders                                 |
| `POST /api/v4/order/modify`                   | Modify an existing order                                    |
| `POST /api/v4/order/market`                   | Place a market order                                        |
| `POST /api/v4/order/stock_market`             | Place a stock market order                                  |
| `POST /api/v4/order/stop_limit`               | Place a stop-limit order                                    |
| `POST /api/v4/order/stop_market`              | Place a stop-market order                                   |
| `POST /api/v4/order/kill-switch`              | Set or cancel the kill-switch timer                         |
| `POST /api/v4/order/kill-switch/status`       | Check kill-switch status                                    |

### Collateral / Margin and Futures trading (14 endpoints)

| Endpoint                                            | Description                     |
| --------------------------------------------------- | ------------------------------- |
| `POST /api/v4/collateral-account/balance`           | Collateral account balance      |
| `POST /api/v4/collateral-account/balance-summary`   | Collateral balance summary      |
| `POST /api/v4/collateral-account/summary`           | Collateral account summary      |
| `POST /api/v4/collateral-account/leverage`          | Set leverage                    |
| `POST /api/v4/collateral-account/positions/open`    | Open positions                  |
| `POST /api/v4/collateral-account/positions/history` | Position history                |
| `POST /api/v4/collateral-account/funding-history`   | Funding history                 |
| `POST /api/v4/oco-orders`                           | OCO orders                      |
| `POST /api/v4/order/collateral/limit`               | Collateral limit order          |
| `POST /api/v4/order/collateral/market`              | Collateral market order         |
| `POST /api/v4/order/collateral/stop-limit`          | Collateral stop-limit order     |
| `POST /api/v4/order/collateral/trigger-market`      | Collateral trigger-market order |
| `POST /api/v4/order/collateral/oco`                 | Collateral OCO order            |
| `POST /api/v4/order/collateral/bulk`                | Collateral bulk orders          |

<Note>
  **API naming convention:** WhiteBIT's API uses "collateral" endpoints for both Margin and Futures trading. The market pair determines the product: spot pairs (e.g., `BTC_USDT`) for Margin, perpetual pairs (e.g., `BTC_PERP`) for Futures. All endpoints under `/api/v4/order/collateral/` and `/api/v4/collateral-account/` serve both products.
</Note>

### Utility (4 endpoints)

| Endpoint                               | Description                                |
| -------------------------------------- | ------------------------------------------ |
| `POST /api/v4/profile/websocket_token` | Generate WebSocket authentication token    |
| `GET /api/v4/public/ping`              | Server health check                        |
| `GET /api/v4/public/time`              | Server time                                |
| `POST /api/v4/market/fee`              | Query maker and taker fees for all markets |

For endpoints not listed above, use the standard WhiteBIT API at `https://whitebit.com`.

## Quoting strategy

Quoting on WhiteBIT uses limit orders placed via individual endpoints or `POST /api/v4/order/bulk` (up to 20 limit orders per request), paired with the `depth` and `bookTicker` WebSocket channels for price input. Orders can be placed over REST — covered in this section — or, from September 2, 2026, over the WebSocket order methods on the same connection (global platform only); see [Placing and canceling orders over WebSocket](#placing-and-canceling-orders-over-websocket) for the trade-offs.

**Bulk vs individual:** bulk reduces wire and auth overhead and gives atomic same-timestamp placement of a quote ladder. The bulk response only returns once every leg in the batch is processed. Pipelined individual orders give faster per-leg acknowledgment for clients optimized for that pattern. Use bulk for atomic quote refreshes and less-optimized clients; use individual orders when lowest per-leg ack latency matters most.

**RPI mode:** Each order item in `/order/bulk` can set `rpi: true` to enable Retail Price Improvement (RPI) mode. RPI orders are post-only and hidden from public `depth` and `bookTicker` feeds. RPI orders remain visible in the exchange UI order book (web/mobile) — capturing UI-driven retail flow rather than algorithmic flow consuming the public feed. RPI executions use an account-specific fee or rebate model. Incompatible with `ioc`. RPI is a flow-segmentation tool, not a default MM primitive — fit depends on volume targets and the account's fee arrangement. See the [API Reference](/api-reference/spot-trading/bulk-limit-order) and [glossary entry](/glossary#retail-price-improvement-rpi).

**Real-time orderbook:** Subscribe to the `depth` WebSocket channel for real-time orderbook updates. See the [WebSocket Quickstart](/guides/websocket-quickstart).

**Order modify:** `POST /api/v4/order/modify` — change an existing order's price, amount, or activation price. The matching engine internally cancels the original order and creates a replacement with a **new `orderId`**, so modify does NOT preserve queue priority. Use `clientOrderId` as the stable identifier across modifications. Identify the target by `orderId` OR `clientOrderId` — never both. See the [API Reference](/api-reference/spot-trading/modify-order). For how the engine sequences orders — price-time priority, participant fairness, and latency factors — see [Matching engine](/concepts/matching-engine).

**Kill-switch (circuit breaker):** `POST /api/v4/order/kill-switch` — sets a per-market timeout of 5–600 seconds; if the endpoint is not called again before it expires, the kill-switch cancels every open order on that market. Quoting several markets requires one timer per market. The deadman trigger for process crashes, lost connectivity, and operator absence. See the [API Reference](/api-reference/spot-trading/sync-kill-switch-timer).

* Configuration: set the timeout (5–600 seconds); each call to the endpoint resets the timer; `timeout: null` deletes it
* Scope: pass the optional `types` array (`"spot"`, `"margin"`, `"futures"`) to restrict the breaker to a subset of order types — useful when spot market-making runs alongside futures positions whose protective orders must stay on the book
* Check status: `POST /api/v4/order/kill-switch/status` — see the [API Reference](/api-reference/spot-trading/status-kill-switch-timer)

**Self-Trade Prevention:** Market makers providing two-sided quotes (bid + ask) must understand STP behavior to avoid self-trades. When a new order would match against an existing order from the same account, the STP mechanism cancels the new order, the existing order, both, or neither — depending on the `stp` mode passed at order placement. The default mode (`no`) allows self-trades, which is usually not what a two-sided quoter wants. See [Self-Trade Prevention](/platform/self-trade-prevention) for the available modes.

The examples below assume an API key with trade permissions. Payload signing (`X-TXC-PAYLOAD`, `X-TXC-SIGNATURE`) is covered in the [request-signing setup](/guides/first-api-call).

<Tabs>
  <Tab title="curl">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Place a bulk order — the "orders" array carries 1–20 limit orders.
    # Each order item has its own "market" field; there is no top-level
    # market parameter.
    curl -X POST "https://whitebit.com/api/v4/order/bulk" \
      -H "Content-Type: application/json" \
      -H "X-TXC-APIKEY: YOUR_API_KEY" \
      -H "X-TXC-PAYLOAD: YOUR_PAYLOAD" \
      -H "X-TXC-SIGNATURE: YOUR_SIGNATURE" \
      -d '{
        "orders": [
          {"market": "BTC_USDT", "side": "buy", "amount": "0.01", "price": "60000", "clientOrderId": "quote-bid-1"}
        ],
        "request": "/api/v4/order/bulk",
        "nonce": 1594297865000
      }'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Request bodies for the quoting loop. To send each one: add "request"
    # (the endpoint path) and "nonce", sign with HMAC-SHA512, and send with
    # the X-TXC-* headers — implementation in the First API Call guide
    # linked above.

    # POST /api/v4/order/bulk — 1–20 limit orders; each item carries its own
    # "market". The response is a list of {"result": ..., "error": ...} pairs —
    # check each item individually; per-order failures do not fail the
    # HTTP request.
    bulk_body = {
        "orders": [
            {"market": "BTC_USDT", "side": "buy", "amount": "0.01", "price": "60000", "clientOrderId": "quote-bid-1"},
        ],
    }

    # POST /api/v4/order/modify — requote by clientOrderId. The response
    # carries a new orderId; clientOrderId is preserved so local tracking
    # survives the modify.
    modify_body = {
        "market": "BTC_USDT",
        "clientOrderId": "quote-bid-1",
        "price": "60050",
    }
    ```
  </Tab>
</Tabs>

For Go and PHP examples, see [SDKs](/sdks).

The kill-switch rides the same loop: arm one timer per quoted market, reset it on every quote refresh, and let expiry cancel that market's open orders if the bot stops calling in.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
    participant Bot as MM bot
    participant WB as WhiteBIT API

    Bot->>WB: POST /api/v4/order/kill-switch (arm timer)
    WB-->>Bot: timer confirmed
    loop every quote refresh — before the timeout expires
        Bot->>WB: POST /api/v4/order/kill-switch (reset timer)
        WB-->>Bot: timer reset
    end
    Bot->>WB: POST /api/v4/order/kill-switch/status (verify active timers)
    WB-->>Bot: active timers for the market
    Note over Bot,WB: bot crashes or loses connectivity — resets stop
    WB->>WB: timeout expires — open orders on the market are canceled
```

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
# POST /api/v4/order/kill-switch — arm or reset the deadman timer.
# "timeout" is a string, "5"–"600" seconds; null deletes the timer.
# The optional "types" array restricts scope, e.g. ["spot"].
# Sign and send as above.
kill_switch_body = {
    "market": "BTC_USDT",
    "timeout": "60",
}
```

## WebSocket workflow for market making

The market-making loop runs on real-time WebSocket data, not REST polling. Quote inputs arrive on `depth` (deep book) or `bookTicker` (top of book); inventory state arrives on `balanceSpot`; fill confirmations arrive on `deals` (executions) and `ordersPending` (state transitions including partial fills and cancels). Wire those four channels together to close the loop: market data drives the quote, REST `order/bulk` or `order/modify` places it, `ordersPending` confirms the state transition, `deals` confirms the fill, `balanceSpot` confirms the inventory delta, and the loop recomputes.

The protocol primitives — connect, ping/pong, `authorize`, exponential-backoff reconnection, and the query-then-subscribe recovery pattern — are covered end-to-end in the [WebSocket Quickstart](/guides/websocket-quickstart).

| Channel         | Carries                                                                      | Recovery model                                                                                                                                        | Where to learn the protocol                                                                                                                                        |
| --------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `depth`         | Full orderbook (1/5/10/20/30/50/100 levels per market)                       | Snapshot on subscribe, then incremental updates; detect gaps via the `past_update_id` chain on deltas (keepalive snapshots are full resets, not gaps) | [Depth channel](/websocket/market-streams/depth)                                                                                                                   |
| `bookTicker`    | Best bid + best ask only — lighter than depth, ideal for top-of-book quoting | Simple replacement on every change                                                                                                                    | [Book Ticker channel](/websocket/market-streams/book-ticker). Note: `bookTicker_update` excludes RPI orders. Track RPI quotes via the private active-orders state. |
| `balanceSpot`   | Available + frozen balances per asset                                        | Query-then-subscribe (`balanceSpot_request` then `balanceSpot_subscribe`)                                                                             | [WS Quickstart — State recovery](/guides/websocket-quickstart#state-recovery-after-reconnect)                                                                      |
| `ordersPending` | Active order state transitions: placed, partially filled, canceled           | Query first (`ordersPending_request` or REST `POST /api/v4/orders`), then subscribe                                                                   | [Bot Guide — WebSocket fill monitoring](/guides/building-a-trading-bot#websocket-fill-monitoring)                                                                  |
| `deals`         | Trade execution events with price, amount, fee, deal-id                      | Query first (`deals_request` or REST `POST /api/v4/trade-account/executed-history`), then subscribe                                                   | [Bot Guide — WebSocket fill monitoring](/guides/building-a-trading-bot#websocket-fill-monitoring)                                                                  |

**Futures additions.** When quoting perpetuals (e.g. `BTC_PERP`), add two more channels:

| Channel                 | Carries                                                                           | Recovery model                                                        | Where to learn the protocol                                                                         |
| ----------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `positions`             | Snapshot of open collateral positions (size, entry price, liquidation price, PnL) | Periodic full snapshot — full state arrives shortly after subscribing | [WS Quickstart — State recovery table](/guides/websocket-quickstart#state-recovery-after-reconnect) |
| `marginPositionsEvents` | Position-change events (open, update, close, liquidation)                         | Event-only — pair with `positions` for complete state                 | [WS Quickstart — State recovery table](/guides/websocket-quickstart#state-recovery-after-reconnect) |

**Canonical subscribe set (spot MM).** Send these messages after the [`authorize` handshake](/websocket/authentication) — the three private subscriptions will be rejected on an unauthenticated socket. For multi-symbol MM, repeat each `*_subscribe` per market or use the multiple-subscription flag where supported (see the [depth subscribe parameters](/websocket/market-streams/depth)). To stop quoting on one pair without disrupting the others, send `depth_unsubscribe` or `bookTicker_unsubscribe` with that market name in `params` — only that market's feed drops; an empty array unsubscribes from all markets on the stream.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {"id": 10, "method": "depth_subscribe",         "params": ["BTC_USDT", 20, "0", true]},
  {"id": 11, "method": "balanceSpot_subscribe",   "params": ["USDT", "BTC"]},
  {"id": 12, "method": "ordersPending_subscribe", "params": ["BTC_USDT"]},
  {"id": 13, "method": "deals_subscribe",         "params": [["BTC_USDT"]]}
]
```

Note the asymmetry: `ordersPending_subscribe` takes a flat array of markets, `deals_subscribe` takes a single-element array containing the array of markets. The full schema lives in `asyncapi/private/deals.yaml` (rendered as the [Deals channel](/websocket/account-streams/deals) page).

For the full reconnect-with-fresh-token + auto-resubscribe pattern that wraps these subscriptions in production, see [WS Quickstart — Reconnection and state recovery](/guides/websocket-quickstart#reconnection-and-state-recovery) and the worked grid-bot example in the [Bot Guide](/guides/building-a-trading-bot#auto-resubscription-after-reconnect).

## Placing and canceling orders over WebSocket

<Note>
  Coming soon — order placement over WebSocket becomes available on September 2, 2026, on the global platform only; the EU platform is not yet supported. Until then, place orders over REST as described in [Quoting strategy](#quoting-strategy).
</Note>

The subscriptions above feed the quoting loop with market data and fill events, while the [Quoting strategy](#quoting-strategy) section places orders over REST. The same authorized socket can carry the write side too: the [WebSocket Order Management](/websocket/order-management/overview) methods place, modify, and cancel orders as JSON-RPC calls on the connection already open for market data — an alternative transport to the REST order endpoints for latency-sensitive flow.

The loop runs on one connection — authorize once, place with an order method, then read the acknowledgment and the fills off the same socket:

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
    participant Bot as MM bot
    participant WS as WhiteBIT WebSocket
    Bot->>WS: authorize (token)
    WS-->>Bot: result: success
    Bot->>WS: order_limit_place (id 101)
    WS-->>Bot: result (id 101 — order object)
    WS-->>Bot: ordersPending push — state change
    WS-->>Bot: deals push — fill
    Note over Bot,WS: correlate the ack by id; fills arrive as id-less pushes
    Bot->>WS: order_modify or order_cancel — requote
```

A placement carries the order parameters in a single-object `params` array — prices and amounts as decimal strings, `side` as `"buy"` or `"sell"`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": 101,
  "method": "order_limit_place",
  "params": [
    { "market": "BTC_USDT", "side": "buy", "amount": "0.01", "price": "60000", "client_order_id": "quote-bid-1" }
  ]
}
```

The server answers with one frame echoing `id`: `result` holds the order object — with `side` inverted to `1` for sell or `2` for buy — and `error` is `null` on success. The full parameter set, the flag rules, and every method live on the [Order Management](/websocket/order-management/overview) reference and the [Place limit order](/websocket/order-management/order-limit-place) page.

**When the socket wins.** Placing over the open connection removes the per-request HTTP round trip and the per-request HMAC signature that each REST order carries: the connection authorizes once, and every later order frame rides that session. For a quoter refreshing one price at a time, the socket is the shortest path to the book.

**When REST still wins.** Two market-making primitives run over REST, so a socket-first quoter keeps a REST client alongside the connection:

* **Atomic ladder placement.** `POST /api/v4/order/bulk` places up to 20 limit orders in one request with same-timestamp atomicity. The WebSocket methods place one order per frame, and order frames draw on the same 200-requests-per-minute-per-connection budget as subscriptions — a 20-level requote is 20 frames against that budget. Refresh a full ladder over REST bulk; place and cancel individual quotes over the socket.
* **The deadman timer.** The kill-switch (`POST /api/v4/order/kill-switch`) that cancels a market's orders when the client stops calling in is a REST endpoint. Arm it over REST no matter which transport places the quotes — the WebSocket `order_cancel_all` method cancels one market on demand but does not run a timer.

**Correlation and errors.** Each order method answers with a single frame carrying the request `id`; responses interleave with subscription pushes, so match answers by `id`, not by arrival order. `order_modify` over the socket is cancel-and-replace exactly as the REST modify is — a new engine order id, `client_order_id` preserved, queue priority lost. The `rpi` flag is accepted on `order_limit_place`, so RPI quoting works over either transport.

<Warning>The WebSocket transport numbers its errors independently of REST — code `40` is `order is not post only`, `41` is `order is not ioc`, and `43` a duplicate `client_order_id`, each different from the REST code for the same condition. Branch on the WebSocket codes in the [error reference](/websocket/order-management/overview#error-codes); never carry a REST code across.</Warning>

## Infrastructure best practices

Three pieces are load-bearing in production: how the account is partitioned across strategies, how the socket recovers when it drops, and how requests stay under per-endpoint limits.

**Sub-accounts for strategy separation:** Use [sub-accounts](/products/sub-accounts/overview) to isolate different trading strategies or pair groups. Each sub-account has independent balances and can have dedicated API keys; transfers between sub-accounts are fee-free.

Worked example: run the BTC\_USDT spot MM book in one sub-account funded with its own USDT balance, and a directional ETH\_USDT swing book in a second sub-account with its own balance and leverage limit. A drawdown that wipes the swing book's collateral cannot pull capital from the MM book — the MM bot keeps quoting on its untouched balance, and the swing book's API key has no authority over the MM sub-account.

**WebSocket connection management:**

* Authenticate after connecting: fetch a token via `POST /api/v4/profile/websocket_token` (rate limit: **10 requests per 60 seconds** — cache the token across reconnects within its lifetime), then send `{"id": N, "method": "authorize", "params": ["<token>", "public"]}` on the socket. On `{"result": {"status": "success"}}`, subscribe to private channels such as `balanceSpot`, `ordersPending`, and `deals`
* Implement automatic reconnection with exponential backoff
* Re-subscribe to all channels after reconnection
* Use the ping/pong mechanism to detect stale connections
* After reconnecting, reconcile local state: call `POST /api/v4/orders` for the current active set and `POST /api/v4/trade-account/executed-history` for fills since the last-seen ID. Do not assume in-memory state survived the disconnect
* See the [WebSocket Quickstart](/guides/websocket-quickstart) for a full connection + auth example

**Rate limit management:**

All limits are per IP address. Order placement, cancellation, and modification sit at 10,000 requests per 10 seconds per endpoint; `trade-account` reads allow 12,000. The outlier is `POST /api/v4/profile/websocket_token` at 10 requests per 60 seconds. Full reference: [Rate Limits](/api-reference/rate-limits).

<ConceptTable
  title="Per-endpoint rate limits — common market-making operations"
  columns={[
{ key: 'endpoint', header: 'Endpoint' },
{ key: 'limit', header: 'Requests / 10 sec' },
]}
  rows={endpointLimits.filter((row) => !row.endpoint.includes('/main-account/'))}
  codeColumns={['endpoint']}
/>

* Use bulk orders (`/order/bulk`) to reduce request count: 20 orders per call vs. 20 individual calls
* Use WebSocket for market data instead of polling REST endpoints
* Maintain a per-endpoint token bucket sized to the table above; back off when the bucket empties rather than retrying after a 429

## Monitoring and safety

Set the kill-switch first; everything below is the loop that keeps it reset, the manual cancel that overrides it, and the balance and fee surfaces the loop reads from.

**Kill-switch configuration:** Set a kill-switch timer for every quoted market as the first action after connecting — each timer covers only its own market. Configure the timeout based on the maximum acceptable unmonitored period for the system. Reset the timers with each regular API call to the kill-switch endpoint. If the system crashes or loses connectivity, the kill-switch cancels each covered market's open orders as its timeout expires.

**Disconnect protection:** WhiteBIT provides no automatic cancel-on-disconnect for WebSocket clients — resting orders persist when the socket drops. The per-market kill-switch is the disconnect safeguard: set a timer for each quoted market and reset it on the connection heartbeat, so a dropped or crashed client has its open orders canceled when the timer lapses. The kill-switch is a REST endpoint — arm it over REST whether quotes are placed over REST or the WebSocket order methods.

**Emergency cancel:** `POST /api/v4/order/cancel/all` — cancel every open order synchronously; the response returns after the cancellations complete. Use the optional `market` field to scope to a single pair and the `type` array to scope to `"spot"`, `"margin"`, or `"futures"`; omit both to cancel everything on the account. This is the "big red button" for live traders; the kill-switch is the deadman timer for unattended operation. `order/cancel/all` is available on colocation as well as through the standard public API. See the [API Reference](/api-reference/spot-trading/cancel-all-orders).

<Tabs>
  <Tab title="curl">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Cancel all spot orders on BTC_USDT
    curl -X POST "https://whitebit.com/api/v4/order/cancel/all" \
      -H "Content-Type: application/json" \
      -H "X-TXC-APIKEY: YOUR_API_KEY" \
      -H "X-TXC-PAYLOAD: YOUR_PAYLOAD" \
      -H "X-TXC-SIGNATURE: YOUR_SIGNATURE" \
      -d '{
        "market": "BTC_USDT",
        "type": ["spot"],
        "request": "/api/v4/order/cancel/all",
        "nonce": 1594297865000
      }'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # POST /api/v4/order/cancel/all — request body; sign and send per the
    # First API Call guide (see Quoting strategy above).
    cancel_all_body = {
        "market": "BTC_USDT",
        "type": ["spot"],
    }
    ```
  </Tab>
</Tabs>

**Balance monitoring:** Use the WebSocket `balanceSpot` channel for real-time balance updates, or poll `POST /api/v4/trade-account/balance` for periodic checks. Monitor for unexpected balance changes.

**Fee tracking:** Use `POST /api/v4/market/fee` to pull the account's global maker and taker fees plus any per-pair custom fees — the `market` request parameter is currently ignored, so the endpoint returns fees for all markets regardless. Filter the per-pair `custom_fee` map client-side. Fee tiers are based on 30-day rolling volume — monitor tier changes as volume accumulates. Look up tier breakpoints on the [VIP program page](https://whitebit.com/vip-program) or the [trading fees page](https://whitebit.com/trading-data/trading-fees).

**Self-Trade Prevention:** Understand the STP mode active on the account. Two-sided quoters hit STP frequently when bid and ask orders overlap. See [Self-Trade Prevention](/platform/self-trade-prevention) for the available modes and behavior.

## How to apply

Contact **[institutional@whitebit.com](mailto:institutional@whitebit.com)** with trading volume history and target markets. The dedicated account manager provides colocation connection details — AWS region, availability zone, and connection endpoints — during onboarding.

## What's next

<CardGroup cols={3}>
  <Card title="Colocation" icon="server" href="/platform/colocation">
    AWS regions, EC2 sizing, and availability-zone placement for colocation onboarding.
  </Card>

  <Card title="Spot Trading API" icon="square-terminal" href="/api-reference/spot-trading/overview">
    Full endpoint documentation for all spot trading endpoints.
  </Card>

  <Card title="Self-Trade Prevention" icon="shield" href="/platform/self-trade-prevention">
    STP modes and behavior for two-sided quoting.
  </Card>
</CardGroup>
