Current open positions + unrealized P&L
curl --request GET \
--url https://api.ironflow.sh/v1/analytics/user-state \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.ironflow.sh/v1/analytics/user-state"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.ironflow.sh/v1/analytics/user-state', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ironflow.sh/v1/analytics/user-state",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.ironflow.sh/v1/analytics/user-state"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.ironflow.sh/v1/analytics/user-state")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ironflow.sh/v1/analytics/user-state")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"address": "0x0c684f333a7e120bce61383da670bbb0157e82d0",
"as_of_ts": 1779394567000,
"asset_positions": [
{
"market": "BTC-PERP",
"market_class": "perp",
"size": 0.1234,
"entry_price": 78000,
"mark_price": 78500,
"position_value": 9690,
"unrealized_pnl": 61.7,
"realized_pnl": 150,
"return_on_equity": 0.00637,
"leverage": 10,
"leverage_type": "cross"
}
],
"margin_summary": {
"total_position_value": 9690,
"total_unrealized_pnl": 61.7,
"total_realized_pnl": 150,
"open_markets_count": 1
}
}{
"error": {
"code": "QUERY_WINDOW_EXCEEDED",
"message": "query time range exceeds maximum 24h0m0s per request for your tier — narrow your from/to window and paginate"
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}{
"error": {
"code": "TIER_FORBIDDEN",
"message": "Export requires Builder tier or above"
}
}{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds."
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred"
}
}{
"error": {
"code": "QUERY_TIMEOUT",
"message": "query took too long — narrow your time range, add a market filter, or reduce the limit"
}
}Wallet Analytics
Hyperliquid open positions and unrealized PnL
GET /v1/analytics/user-state returns an address’s open Hyperliquid perp positions, entry and mark prices and unrealized PnL, in the clearinghouseState shape.
GET
/
v1
/
analytics
/
user-state
Current open positions + unrealized P&L
curl --request GET \
--url https://api.ironflow.sh/v1/analytics/user-state \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.ironflow.sh/v1/analytics/user-state"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.ironflow.sh/v1/analytics/user-state', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ironflow.sh/v1/analytics/user-state",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.ironflow.sh/v1/analytics/user-state"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.ironflow.sh/v1/analytics/user-state")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ironflow.sh/v1/analytics/user-state")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"address": "0x0c684f333a7e120bce61383da670bbb0157e82d0",
"as_of_ts": 1779394567000,
"asset_positions": [
{
"market": "BTC-PERP",
"market_class": "perp",
"size": 0.1234,
"entry_price": 78000,
"mark_price": 78500,
"position_value": 9690,
"unrealized_pnl": 61.7,
"realized_pnl": 150,
"return_on_equity": 0.00637,
"leverage": 10,
"leverage_type": "cross"
}
],
"margin_summary": {
"total_position_value": 9690,
"total_unrealized_pnl": 61.7,
"total_realized_pnl": 150,
"open_markets_count": 1
}
}{
"error": {
"code": "QUERY_WINDOW_EXCEEDED",
"message": "query time range exceeds maximum 24h0m0s per request for your tier — narrow your from/to window and paginate"
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}{
"error": {
"code": "TIER_FORBIDDEN",
"message": "Export requires Builder tier or above"
}
}{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds."
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred"
}
}{
"error": {
"code": "QUERY_TIMEOUT",
"message": "query took too long — narrow your time range, add a market filter, or reduce the limit"
}
}When to use
Useuser-state when you need a point-in-time picture of where an
address sits on Hyperliquid — currently open markets, position sizes,
entry prices, mark prices, and unrealized P&L. This is the canonical
“what are they holding right now?” endpoint.
For history (how those positions evolved, individual fills, funding
payments), use /v1/fills,
/v1/analytics/user-funding,
or /v1/analytics/user-maker-taker.
Conventions
sizeis signed: positive = long, negative = short. Markets with|size| < 1e-9are filtered (effectively closed).unrealized_pnl = size * (mark_price - entry_price).entry_priceis the VWAP entry across all fills that built the current position; matches HL’sclearinghouseState.assetPositions[].entryPx.as_of_ts(unix ms) carries the wall-clock of the latestmark_pricesrow used. If this is more than a few seconds old, the mark-price poller is lagging; the position size + entry are still accurate but the unrealized P&L will be stale.
Example
curl -H "Authorization: Bearer $IRONFLOW_API_KEY" \
"https://api.ironflow.sh/v1/analytics/user-state?address=0x0c684f333a7e120bce61383da670bbb0157e82d0"
Authorizations
Ironflow API key, format if_xxxx. Free keys at https://ironflow.sh/key. Optional on read endpoints.
Query Parameters
Wallet address filter (hex string, e.g. 0xabc123).
Example:
"0xabc123def456"
Response
Current state for the address.
Snapshot of an address's current open positions + uPnL.