> Treat every fee rate, margin ratio, order limit, minimum and address in this documentation as an illustration of the shape of the value, never as a constant to hardcode - the live values are served by GET /markets and GET /config on the Perps API.

# Perps API Overview

> Programmatic access to HyperFlow Perps — accounts, orders, and funds.

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).](/_fern-files/hyperflowdefi.docs.buildwithfern.com/39c391d3009714bddbbcb7545a66b5c8f68600ca4bbe5ff0e6c5764093b95b6b/docs/assets/diagrams/perps-api-flow.svg)

## The pages in this section

#### [Authentication & Signing](/developers/perps-api/authentication)

Trading keys, the EIP-712 domain, and the exact typed-data schemas.

#### [Orders](/developers/perps-api/trading)

Submit, cancel, modify — including post-only, trigger (TP/SL), bracket orders, and batching.

#### [Position Lifecycle](/developers/perps-api/position-lifecycle)

Enter, protect, adjust, and exit — in the order the API requires.

#### [Account Data](/developers/perps-api/account)

Balance, positions, open orders, history, and margin adjustments.

#### [Deposits & Withdrawals](/developers/perps-api/funds)

Funding config, deposit tracking, and signed withdrawals.

## Response envelope

Every endpoint wraps its payload in the same envelope:

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

Errors return `success: false` with an `error` object:

```json
{ "success": false, "error": { "code": "...", "message": "..." }, "timestamp": 1787067314951 }
```

| `error.code`                                                                                   | HTTP  | Meaning                                                                                                                            |
| ---------------------------------------------------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_REQUEST`                                                                              | `400` | Validation failed; the message names the field                                                                                     |
| `INVALID_SIGNATURE`, `INVALID_ENVIRONMENT`, `INVALID_EXPIRES_AT`, `INVALID_SIGNATURE_CHAIN_ID` | `400` | Key registration and revocation                                                                                                    |
| `UNAUTHORIZED`                                                                                 | `401` | Wallet signature on `/account/withdraw` did not verify                                                                             |
| `FORBIDDEN`                                                                                    | `403` | Trading-key action: no active key for the recovered signer — [two causes](/developers/perps-api/authentication#signature-failures) |
| `NOT_FOUND`                                                                                    | `404` | No such order, request, or withdrawal — or not persisted yet                                                                       |
| —                                                                                              | `409` | Replayed `nonce`                                                                                                                   |
| `INTERNAL_ERROR`                                                                               | `500` | Server-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:

| Identifier   | What it is                                          |
| ------------ | --------------------------------------------------- |
| `wallet`     | Your ordinary EVM address, e.g. `0xAbC…123`         |
| `account_id` | The 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>
```

```ts
const accountId = (wallet.toLowerCase() + "00".repeat(11) + "00") as `0x${string}`;
```

> **Note**
>
> 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](/developers/perps-api/authentication).

## Market data

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

| Data                                              | Source                                                 |
| ------------------------------------------------- | ------------------------------------------------------ |
| Order book                                        | HL WebSocket `l2Book` subscription                     |
| Trades                                            | HL WebSocket `trades` subscription                     |
| Candles                                           | HL REST `POST /info` `{"type": "candleSnapshot", ...}` |
| Mark / oracle price, funding rate                 | HL 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](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api).

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):

```json
{
  "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](/developers/perps-api/trading#price-and-size-rules).

## Practical limits

Fixed by the API:

| Limit                                   | Value |
| --------------------------------------- | ----- |
| Orders per batch                        | 20    |
| Cancels per batch                       | 20    |
| `limit` on wallet-keyed paginated reads | 200   |

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

| Field             | Today | Meaning                                                                                                                 |
| ----------------- | ----- | ----------------------------------------------------------------------------------------------------------------------- |
| `min_order_value` | \$10  | Smallest order notional. Reduce-only orders are exempt                                                                  |
| `max_notional`    | `"0"` | No maximum order value. `0` is the no-ceiling sentinel                                                                  |
| `max_open_orders` | 20    | Open 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](https://discord.gg/GGDmD4K9CK).

**Next:** [Authentication & Signing](/developers/perps-api/authentication)