Skip to navigation

Perps API Overview

The Perps API is the same backend the HyperFlow app trades through. It gives you programmatic access to markets, order placement, account state, and transfers.

Base URL: https://perps-api.hyperflow.fun/api/v1 — mainnet only. No invite or allowlisting is required; any wallet can register a trading key and trade.

Integration at a glance

Four steps: 1 Enable trading (POST /auth/register — main wallet authorizes an agent, once), 2 Deposit (spot transfer, poll deposit-history), 3 Trade (POST /orders, cancel, modify, margin), 4 Withdraw (POST /account/withdraw, poll withdrawal-history).

The pages in this section

Response envelope

Every endpoint wraps its payload in the same envelope:

{ "success": true, "data": { "...": "..." }, "timestamp": 1787067314951 }

Errors return success: false with an error object:

{ "success": false, "error": { "code": "...", "message": "..." }, "timestamp": 1787067314951 }
error.codeHTTPMeaning
INVALID_REQUEST400Validation failed; the message names the field
INVALID_SIGNATURE, INVALID_ENVIRONMENT, INVALID_EXPIRES_AT, INVALID_SIGNATURE_CHAIN_ID400Key registration and revocation
UNAUTHORIZED401Wallet signature on /account/withdraw did not verify
FORBIDDEN403Trading-key action: no active key for the recovered signer — two causes
NOT_FOUND404No such order, request, or withdrawal — or not persisted yet
—409Replayed nonce
INTERNAL_ERROR500Server-side failure

Batch and asynchronous routes return 200 with the outcome per item in data (status, reason); cancel-batch can return 200 with success: false. Read the items, not the envelope.

  • timestamp is Unix milliseconds.
  • Prices, sizes, and amounts are decimal strings ("0.00159", "2000") — never floats. (Non-monetary ratios like /config’s trader_share are plain JSON numbers.)

Account format

Endpoints identify you by one of two values, and each endpoint’s reference states which one it takes:

IdentifierWhat it is
walletYour ordinary EVM address, e.g. 0xAbC…123
account_idThe id the ledger keys on, derived from that wallet

Derive the account id by right-padding the wallet with a subaccount tail. Index 0 is your default account, so in practice the account id is the lowercase wallet followed by 24 hex zeros:

account_id = <wallet address> || <11 zero bytes> || <1-byte subaccount index>
const accountId = (wallet.toLowerCase() + "00".repeat(11) + "00") as `0x${string}`;

Derive it once and keep it. Every other page in this section refers to these two values by name only. Only index 0 is addressable today.

Authentication model

There is no transport-level auth — no API keys, no bearer tokens. Instead:

  • Reads need no signature. They are keyed by wallet or account id, so you can query your own state without holding a key. Treat your account data as readable, not private.
  • Writes are signed. Every state-changing request carries an EIP-712 signature in the body plus a single-use millisecond nonce, and the server recovers the signer from it — see Authentication & Signing.

Market data

HyperFlow does not proxy market data — read it from Hyperliquid’s public API, which is the venue of execution:

DataSource
Order bookHL WebSocket l2Book subscription
TradesHL WebSocket trades subscription
CandlesHL REST POST /info {"type": "candleSnapshot", ...}
Mark / oracle price, funding rateHL WebSocket activeAssetCtx
Market config (fees, tick size, leverage, limits)HyperFlow GET /markets

Hyperliquid endpoints: https://api.hyperliquid.xyz (REST) and wss://api.hyperliquid.xyz/ws (WebSocket) — see the Hyperliquid API docs.

Join on hl_symbol or hl_asset_index, not symbol. The HyperFlow API does not serve mark prices. Hyperliquid funding is hourly.

GET /markets

Static per-market configuration. Live response (trimmed):

{
"data": [{
"symbol": "BTC",
"hl_symbol": "BTC",
"hl_asset_index": 0,
"max_leverage": 20,
"display_max_leverage": 20,
"initial_margin_ratio": "0.025",
"maintenance_margin_ratio": "0.015",
"maker_fee_rate": "0.00015",
"taker_fee_rate": "0.00045",
"min_order_value": "10",
"max_notional": "0",
"max_open_orders": 20,
"slippage_bps": 50,
"tick_size": "1",
"sz_decimals": 5,
"px_decimals": 1,
"only_isolated": false,
"is_active": true
}],
"success": true,
"timestamp": 1787067314951
}
  • max_notional: "0" is the no-ceiling sentinel — treat it as “no maximum order value”, never as a zero cap.
  • slippage_bps is the default slippage bound the app applies to market orders; you are free to compute your own.
  • sz_decimals / px_decimals bound how many decimals a size and a price may carry; tick_size is the price increment and binds harder than px_decimals — see Price and size rules.

Practical limits

Fixed by the API:

LimitValue
Orders per batch20
Cancels per batch20
limit on wallet-keyed paginated reads200

Set per market, and returned by GET /markets — read them at runtime rather than hardcoding, because they change:

FieldTodayMeaning
min_order_value$10Smallest order notional. Reduce-only orders are exempt
max_notional"0"No maximum order value. 0 is the no-ceiling sentinel
max_open_orders20Open orders per market, per account — resting orders, armed triggers, and TP/SL still waiting for their entry all count

Not published: rate limits, the withdrawal ceilings behind ABOVE_MAX / DAILY_LIMIT_EXCEEDED, and a changelog — schema changes are announced on Discord.

Next: Authentication & Signing