Skip to main content
Perps positions use the same position API structure as spots, with a few additional fields. This guide covers what’s different, and how to handle perps in your integration.

Spot vs Perps: What Changes

A spot position tracks tokens bought and sold onchain. A perps position tracks a leveraged long or short on Hyperliquid. The API returns both through the same position-fetching endpoints. Perps positions are distinguished by three fields on metadata (plus three new properties on metadata.positionStats, discussed later):
FieldTypeDescription
perpPositionType"long" | "short" | nullPosition direction. Present only on perps.
perpLeveragenumber | nullLeverage multiplier (e.g. 2, 5, 10).
positionAmountWithLeveragenumberToken amount with leverage applied.
If perpPositionType is null or absent, the position is spot.

Perps Fields in Detail

Position Direction and Leverage

{
  "metadata": {
    "perpPositionType": "long",
    "perpLeverage": 5,
    "positionAmount": 0.5,
    "positionAmountWithLeverage": 2.5
  }
}
perpPositionType tells you whether the trader is long or short. perpLeverage is the multiplier. positionAmount is the base amount (the margin denominated in the token), and positionAmountWithLeverage is the notional exposure (positionAmount * perpLeverage). For shorts on Hyperliquid, positionAmount can be negative.

Position Stats

Perps positions add several fields to positionStats:
FieldTypeDescription
holdingsCostBasisUSDnumberThe margin (collateral) in USD
holdingsCostBasisUSDWithLeveragenumberThe leveraged cost basis in USD
boughtUSDWithLeveragenumberTotal USD entered with leverage applied
soldUSDWithLeveragenumberTotal USD exited with leverage applied
For spot positions, holdingsCostBasisUSD is the total cost of the tokens currently held. For perps, it represents the margin posted. The WithLeverage variants reflect the full notional values.
{
  "metadata": {
    "positionStats": {
      "holdingsCostBasisUSD": 1000,
      "holdingsCostBasisUSDWithLeverage": 5000,
      "boughtUSD": 1000,
      "boughtUSDWithLeverage": 5000,
      "soldUSD": 0,
      "soldUSDWithLeverage": 0,
      "realizedGainsUSD": 0
    }
  }
}
In this example, the trader posted $1,000 margin with 5x leverage, giving $5,000 notional exposure.

Metadata vs Trade-Level Props

perpPositionType and perpLeverage exist in two places: on metadata (position-level) and on each object in the trades array. They serve different purposes. Metadata-level (metadata.perpPositionType, metadata.perpLeverage) reflects the current state of the position. Use these for:
  • Position cards, profile pages, and token screens
  • PnL calculations
  • Detecting whether a position is perps or spot
  • Displaying the current direction and leverage
For most integrations, the metadata-level fields are all you need. Trade-level (trade.perpPositionType, trade.perpLeverage) reflects what happened at the time of that specific trade. Use these when rendering individual trade activity. The trade-level leverage matters because it tells you the leverage that trade used, which is what you want when describing the action:
"alice 5x longed $2,000 of ETH"        ← trade.perpLeverage
"alice closed their 5x long ETH"       ← trade.perpLeverage
"alice closed $500 of their 5x long ETH" ← trade.perpLeverage

Computing Unrealized PnL

For spot positions, unrealized PnL is:
unrealizedPnL = (positionAmount * currentPrice) - holdingsCostBasisUSD
For perps, use the leveraged values:
leveragedNotional = positionAmountWithLeverage * currentPrice
Then compute PnL based on direction:
// Long
unrealizedPnL = leveragedNotional - holdingsCostBasisUSDWithLeverage

// Short
unrealizedPnL = holdingsCostBasisUSDWithLeverage - leveragedNotional
The margin (holdingsCostBasisUSD) is what the trader actually put up. The leveraged cost basis is what determines PnL.

Perps Trade Actions

If you’re building activity feeds or notifications, you can use the trades array to produce more descriptive copy than “entered” or “exited.” The Clicker app has copy for four different types of actions:
ActionHow to deriveExample copy
openintent is "enter" and it’s the first trade”opened a 5x long on ETH”
addintent is "enter" and prior trades exist”5x longed $2,000 more of ETH”
partial-closeintent is "exit" and the position still has balance”closed $500 of their 5x long ETH”
full-closeintent is "exit" and the position is fully closed”closed their 5x long ETH”