Added

v1.16.0 - 2026-02-13

Added

  • New endpoint: POST /heartbeat for automatic order cancellation (dead man's switch)

    Automated trading systems can now send periodic heartbeat pings. If no heartbeat is received within the configured timeout, all open orders for the maker are automatically cancelled.

    Request example:

    // With EIP-712 signature
    const response = await fetch('/heartbeat', {
      method: 'POST',
      headers: {
        'x-apikey': API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        maker: '0xYourAddress',
        timeout: 60,  // Cancel all orders if no ping within 60 seconds
        signature: '0x...',
        signature_deadline: Math.floor(Date.now() / 1000) + 30
      })
    });
    
    // With one-click session (no signature needed)
    const response = await fetch('/heartbeat', {
      method: 'POST',
      headers: {
        'x-apikey': API_KEY,
        'x-one-click': SESSION_HASH,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        maker: '0xYourAddress',
        timeout: 60
      })
    });

    Replay protection: When using signatures, signature_deadline must be strictly greater than the last accepted deadline for the maker (deadline monotonicity). Deadlines are bounded to 30 seconds in the future.

    Benefits: Prevents stale orders from sitting on the book when trading bots crash or lose connectivity.

  • New EIP-712 signature type: HeartbeatType

    8th signature type added for signing heartbeat pings:

    const HeartbeatType = [
      { name: 'deadline', type: 'uint256' },
      { name: 'maker', type: 'address' },
      { name: 'timeout', type: 'uint256' },
    ];
  • WebSocket server message envelope fields: message_id and seq_id

    All server messages now include two new fields for reliability:

    • message_id (string): UUID v4 unique to each message — use for deduplication
    • seq_id (number): Monotonically increasing per connection, starting at 1 — use for gap detection
    {
      "kind": "event",
      "type": "trade",
      "timestamp_ms": 1677721600000,
      "message_id": "550e8400-e29b-41d4-a716-446655440000",
      "seq_id": 42,
      "data": { ... }
    }

    Benefits: Enables reliable message processing — detect missed messages via seq_id gaps and avoid processing duplicates via message_id.

  • New WebSocket message type: resend for message recovery

    When a gap in seq_id is detected, request the server to re-deliver missed messages:

    {
      "type": "resend",
      "begin_seq_id": 10,
      "end_seq_id": 15
    }
    • Maximum range: 100 messages per request
    • Messages are cached per connection with limited capacity (LRU eviction) and TTL
    • Resent messages retain their original seq_id and message_id
    • Rate limited: 5 requests per 10 seconds

    Benefits: Enables reliable, lossless event processing without requiring full resubscription after brief network hiccups.

  • New error codes for enhanced validation feedback

    Three new error codes added to StandardError:

    • SIGNATURE_DEADLINE_STALE — Signature deadline is not strictly greater than the last accepted deadline (replay protection)
    • ONE_CT_USER_MISMATCH — One-click trading session was created by a different address than the maker/taker in the request
    • VALUE_INVALID — A request field has an invalid value (e.g., heartbeat timeout out of range)

Changed

  • Breaking Change: Market and combo trade responses simplified

    The unprocessedOrders and rejectedOrders fields have been removed from POST /market and POST /combo responses.

    Before:

    {
      "fillResult": { ... },
      "filledOrders": [...],
      "rejectedOrders": [...],
      "unprocessedOrders": [...]
    }

    After:

    {
      "fillResult": { ... },
      "filledOrders": [...]
    }

    Migration: Remove references to rejectedOrders and unprocessedOrders from response handling code. All successfully processed orders appear in filledOrders.

    Benefits: Cleaner response structure — these fields were vestigial and always empty in practice.

  • PATCH /limit now triggers IOC matching for non-post-only orders crossing the market

    When editing a non-post-only order to a price that crosses the market (e.g., buy price above best ask), the order triggers immediate matching against resting orders (IOC-style). The order's filled_amount resets to 0 and the new size is used for matching.

    Before: Edited orders that crossed the market were rejected.

    After: Non-post-only edited orders that cross the market are immediately matched. Post-only orders crossing the market are still rejected with post only violation.

    Benefits: More flexible order editing for aggressive traders — modify orders without needing to cancel and re-submit.

  • Price and size increment validation now applies to liquidation orders

    Liquidation orders are no longer exempt from price and size increment constraints. All orders (including liquidation: true) must comply with the same increment rules returned by GET /api/v1/exchange_info.

    Benefits: Consistent validation across all order types ensures orderbook integrity.

  • Account history endpoint sorting enhanced

    The sort parameter has been replaced with two parameters:

    • sortKey: Field to sort by — timestamp (default) or realized_pnl
    • sortOrder: Direction — desc (default) or asc

    Response now includes total_count and total_pages when available.

    Before:

    GET /account_history?address=0x...&sort=desc

    After:

    GET /account_history?address=0x...&sortKey=timestamp&sortOrder=desc
    GET /account_history?address=0x...&sortKey=realized_pnl&sortOrder=asc

    Migration: Replace sort parameter with sortKey + sortOrder.

    Benefits: Sort by realized P&L for performance analysis; total counts enable proper pagination UI.

  • POST /calculate_user_risk response: future_settlement_projections now required

    The future_settlement_projections field is now always present in the response (previously optional).

  • WebSocket rate limit corrections

    Message TypeRate LimitWindow (corrected)
    subscribe10 requests1 second (was documented as 10 seconds)
    unsubscribe10 requests1 second (was documented as 10 seconds)
    unsubscribe_all1 request1 second (was 10 req/10s)

Documentation

  • Added POST /heartbeat endpoint to OpenAPI specification
  • Added HeartbeatType as 8th EIP-712 signature type to signatures guide
  • Added resend message type with full request/response examples to WebSocket documentation
  • Added server message envelope fields (message_id, seq_id) section to WebSocket documentation
  • Added gap detection example code to WebSocket documentation
  • Updated rate limit table with resend entry and corrected windows
  • Clarified WebSocket heartbeat note: "no WebSocket-level ping/pong" vs REST heartbeat endpoint