> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xarchive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Account positions

> Read open perp positions, hourly history, change logs, account summaries, and market-wide positioning for Hyperliquid, HIP-3, and Lighter accounts.

Read public perpetual positions on Hyperliquid core, HIP-3, Lighter, and Lighter on Robinhood Chain by wallet, account index, or market: current snapshots, historical state within the available coverage, position changes, and account and market summaries.

These are read-only records derived from public venue data. They do not provide access to a private venue account, spot balances, or order execution. Authenticate with your 0xArchive API key, not a venue credential or wallet private key.

<Note>
  Check `meta.as_of`, `meta.stale`, and the quality fields before using a result. Current positions are periodic snapshots, not a real-time account feed. For historical reads, also check coverage and finality. See [Timing and provenance](#timing-and-provenance), [Finality and freshness](#finality-and-freshness), and [Quality](#quality).
</Note>

## Venues and account keys

Positions are published for Hyperliquid core perps, HIP-3 builder perps, Lighter mainnet, and [Lighter on Robinhood Chain](/rest-api/rh-lighter). Spot markets and HIP-4 outcome markets have no positions routes.

| Venue | Namespace, account key, and snapshot refresh |
| - | - |
| Hyperliquid core | `/v1/hyperliquid`. `0x` address. Every five minutes |
| HIP-3 | `/v1/hyperliquid/hip3`. `0x` address, optional `dex`. Every five minutes |
| Lighter mainnet | `/v1/lighter`. Integer account index. Every two minutes |
| Lighter on Robinhood Chain | `/v1/rh-lighter`. Integer account index. Every two minutes |

Hyperliquid addresses are 42-character hex strings starting with `0x` and are case-insensitive. Lighter paths take integer account indexes, not wallet addresses. Use the [L1 address lookup](#lighter-l1-address-lookup) to find an address's Lighter mainnet accounts. Mainnet and Robinhood Chain account indexes are separate identifiers, even when their numbers match.

### Historical coverage

| Venue | Coverage starts (UTC) |
| - | - |
| Hyperliquid core | Change log and as-of reads from May 25, 2025 15:00. Hourly snapshots from Jun 7, 2026 18:00 |
| HIP-3 | Change log and as-of reads from Oct 13, 2025 00:00. Hourly snapshots from Jun 7, 2026 18:00; later for some dexes |
| Lighter mainnet | Change log, as-of reads, and hourly snapshots from Jan 17, 2025 00:00 |
| Lighter on Robinhood Chain | Change log, as-of reads, and hourly snapshots from Jun 26, 2026 00:00 |

A request before coverage returns `200` with an empty result, `meta.coverage_from`, and a `meta.notice` identifying the coverage start. This applies to as-of reads and change logs on every venue, and to hourly position history on both Lighter deployments. Hyperliquid and HIP-3 hourly position history before the first snapshot returns an empty page without a notice. An empty result outside coverage does not mean the account had no positions.

## Routes

Every route is `GET`. Prefix each path with the venue namespace from the table above.

Account routes use `/wallets/{address}/...` on Hyperliquid and HIP-3, and `/accounts/{account_index}/...` on Lighter and Robinhood Chain. Market and bulk routes use the same path on every venue.

| Route | Path and what it returns |
| - | - |
| Account positions | `/wallets/{address}/positions` or `/accounts/{account_index}/positions`. Open positions now, or at `timestamp`; `data.account` is a summary or `null` |
| Hourly history | `/wallets/{address}/positions/history` or `/accounts/{account_index}/positions/history`. One row per open position per available hourly snapshot in `[start, end)` |
| Change log | `/wallets/{address}/positions/changes` or `/accounts/{account_index}/positions/changes`. Trade, liquidation, deleverage, and settlement legs, oldest first |
| Account summary | `/wallets/{address}/account` or `/accounts/{account_index}/account`. The latest account summary |
| Account history | `/wallets/{address}/account/history` or `/accounts/{account_index}/account/history`. Hourly account summaries in `[start, end)` |
| Market positions | `/positions/{symbol}`. Every open position in one market, largest value first, with totals |
| Market summary | `/positions/{symbol}/summary`. Long and short counts, sizes, values, average entries, and top-10 share, now or as an hourly series |
| Bulk | `/positions`. Every open position across all markets at one hour |

Related routes:

* `GET /v1/lighter/accounts?l1_address=0x...` lists the Lighter mainnet account indexes an L1 address owns. Robinhood Chain has no L1 lookup.
* `GET /v1/data-quality/positions` reports, per venue, the latest live snapshot and its age, the latest hourly snapshot, `built_through`, and `finalized_through`.

See [OpenAPI](/openapi) for the full request and response schemas.

## First request

Set `OXARCHIVE_API_KEY` in your environment and set `WALLET_ADDRESS` to the public Hyperliquid address you want to query. Keep API keys in server-side code or a local shell, not browser code or source control.

```bash theme={"theme":"github-dark"}
base_url="https://api.0xarchive.io/v1/hyperliquid"
curl "${base_url}/wallets/${WALLET_ADDRESS}/positions" \
  -H "X-API-Key: $OXARCHIVE_API_KEY"
```

The response contains `data.positions` and `data.account`. The account summary can be `null`; see [Account summary](#account-summary). An empty positions result also includes `data.account_seen`.

This illustrative response uses synthetic values. It is not a captured account response or a prediction of what your request will return.

<Accordion title="Example snapshot response">
  ```json theme={"theme":"github-dark"}
  {
    "success": true,
    "data": {
      "positions": [
        {
          "symbol": "BTC",
          "coin": "BTC",
          "size": "-1",
          "side": "short",
          "entry_price": "65000",
          "mark_price": "64000",
          "mark_time": "2026-09-01T11:59:31Z",
          "position_value": "64000",
          "unrealized_pnl": "1000",
          "return_on_equity": "0.15625",
          "leverage": { "type": "cross", "value": "10" },
          "max_leverage": 40,
          "margin_used": "6400",
          "liquidation_price": null,
          "liquidation_price_status": "not_published_cross",
          "cum_funding": { "all_time": "0", "since_open": "0", "since_change": "0" },
          "opened_at": "2026-09-01T08:00:00Z",
          "snapshot_as_of": "2026-09-01T12:06:12Z",
          "quality": "complete"
        }
      ],
      "account": {
        "account_value": "11000",
        "cross_account_value": "11000",
        "collateral": "10000",
        "total_margin_used": "6400",
        "cross_maintenance_margin_used": "800",
        "withdrawable": "4600",
        "total_position_value": "64000",
        "total_unrealized_pnl": "1000",
        "long_value": "0",
        "short_value": "64000",
        "n_positions": 1,
        "account_mode": "unified",
        "snapshot_as_of": "2026-09-01T12:06:12Z",
        "quality": "complete"
      }
    },
    "meta": {
      "count": 1,
      "request_id": "550e8400-e29b-41d4-a716-446655440000",
      "as_of": "2026-09-01T12:00:00.000Z",
      "snapshot_ts": "2026-09-01T12:00:00.000Z",
      "source": "snapshot",
      "quality": "complete",
      "stale": false
    }
  }
  ```
</Accordion>

For Lighter mainnet, set `LIGHTER_ACCOUNT_INDEX` to the account index returned by the [L1 lookup](#lighter-l1-address-lookup):

```bash theme={"theme":"github-dark"}
base_url="https://api.0xarchive.io/v1/lighter"
curl "${base_url}/accounts/${LIGHTER_ACCOUNT_INDEX}/positions" \
  -H "X-API-Key: $OXARCHIVE_API_KEY"
```

## Current, as-of, and reconstructed state

Without `timestamp`, the account positions routes return the latest snapshot. `meta.stale` is `true`, with a `meta.notice`, when that snapshot is more than 12 minutes old.

With `timestamp` (epoch milliseconds), the response is the state after every event before that instant:

* An exact UTC hour with an available hourly snapshot returns that snapshot. `meta.source` is `snapshot`.
* Other instants return reconstructed state, with `meta.source: "reconstructed"`. Size, entry price, and `opened_at` are exact as of that instant. The mark is the last one at or before it and at most 5 minutes old; without one, mark price, value, and unrealized PnL are `null`. Check row quality and nullable fields rather than assuming a complete snapshot.
* A request later than the available change history is limited to `meta.built_through`. The response adds `meta.requested_end` and `meta.clamped_to` so you can detect the difference.
* An instant before coverage returns no positions, `data.account_seen: "outside_coverage"`, a `meta.notice`, and `meta.coverage_from`.

Reconstructed responses retain the position schema, but snapshot-only values are `null` by design, and `data.account` is `null`:

| Field | Reconstructed response |
| - | - |
| `leverage` | Hyperliquid and HIP-3: `{"type": "unknown", "value": null}`. Lighter keeps its margin mode in `type`, with `value: null` |
| `cum_funding` | The object remains present; all members are `null` |
| `return_on_equity`, `max_leverage`, `margin_used`, `liquidation_price`, `snapshot_as_of` | `null` |
| `liquidation_price_status` | `unavailable` |

`meta.as_of` identifies the time the returned data describes, not the request time. Market routes read snapshots only: pass `hour` as an exact UTC hour in epoch milliseconds, and check the resolved snapshot in `meta.snapshot_ts`.

### Empty results

When an account holds no positions on the first page, `data.account_seen` says why:

| Value | Meaning |
| - | - |
| `flat` | The account has recorded activity in covered history and holds nothing at this instant |
| `never_seen` | No activity is recorded for the account in covered history up to this instant; `meta.notice` and `meta.coverage_from` scope the claim, because activity before coverage cannot be ruled out |
| `outside_coverage` | The instant is before the venue's change-log coverage |

## Timing and provenance

Hyperliquid and HIP-3 responses carry three times, which can differ by more than an hour:

| Field | Meaning |
| - | - |
| `meta.as_of` | The instant the response describes: the current snapshot's time, the exact hour, or the requested reconstruction instant |
| `meta.snapshot_ts` | The snapshot the response was read from, present only when `meta.source` is `snapshot`. History and bulk rows carry their hour in their own `snapshot_ts` |
| `snapshot_as_of` | On each position and on the account: when the leverage, margin, funding, liquidation-price, and account-balance values were taken. It can be before or after `meta.as_of`. It is `null` on reconstructions and on rows the observation did not cover up to 2026-09-26 18:00 |

Where each field comes from depends on the time you read:

**Hourly reads from 2026-06-07 18:00 to 2026-09-26 18:00 UTC**

* `size`, `entry_price`: exact at the hour, from the change log.
* `opened_at`: exact at the hour.
* `mark_price`, `position_value`, `unrealized_pnl`: last mark in the 5 minutes up to the hour.
* `leverage`, `max_leverage`, `cum_funding`, isolated `liquidation_price`, account balances: Hyperliquid's own values from an observation of the account, usually made after the hour.
* `snapshot_as_of`: time of that observation, typically 5 to 14 minutes after the hour for core perps, longer for HIP-3, rarely up to about an hour.

**Hourly reads from 2026-09-26 19:00 UTC, and current reads**

* `size`, `entry_price`: exact at `meta.as_of`, an exchange-state snapshot rolled forward with every fill.
* `opened_at`: set only when the position opened after the exchange-state snapshot; otherwise `null`.
* `mark_price`, `position_value`, `unrealized_pnl`: last mark in the 5 minutes up to `meta.as_of`.
* `leverage`, `max_leverage`, `cum_funding`, isolated `liquidation_price`, account balances: from the exchange-state snapshot, taken before `meta.as_of`.
* `snapshot_as_of`: time of that exchange-state snapshot, up to about 100 minutes before the hour.

**Any other timestamp**

* `size`, `entry_price`, `opened_at`: exact at the timestamp.

* `mark_price`, `position_value`, `unrealized_pnl`: last mark in the 5 minutes up to the timestamp.

* `leverage`, `max_leverage`, `cum_funding`, isolated `liquidation_price`, account balances: not provided.

* `snapshot_as_of`: `null`.

* **Up to 2026-09-26 18:00**, values observed after the hour can include changes after it, such as the funding settled on the hour. In a small share of rows (about 4% for core perps and 2% for HIP-3) the observation was made before the hour instead, by up to about 97 minutes. If the position had fills between the hour and the observation, `margin_used`, the liquidation price, and `cum_funding.since_open` and `since_change` are `null`. A position the observation did not cover has `leverage.type: "unknown"`, `null` snapshot values and `snapshot_as_of`, and row `quality: "partial"`.

* **From 2026-09-26 19:00, and in current snapshots**, these values never include data from after `meta.as_of`, but can be up to about 100 minutes older than it. A position opened after the exchange-state snapshot has `leverage.type: "unknown"` and no margin, funding, or liquidation price. To get `opened_at` for a position already open at the snapshot, read the change log or request a timestamp that is not an exact hour.

* **Account summaries:** balances (`account_value`, `cross_account_value`, `collateral`, `total_margin_used`, `cross_maintenance_margin_used`, `withdrawable`) describe `snapshot_as_of`. Totals (`total_position_value`, `total_unrealized_pnl`, `long_value`, `short_value`, `n_positions`) describe `meta.as_of`, like the positions.

### Liquidation price status

| `liquidation_price_status` | Meaning |
| - | - |
| `exact` | An isolated position with a published price. Up to 2026-09-26 18:00: Hyperliquid's own value from the observation, when the position had no fills between the hour and the observation and the observed size matches. From 2026-09-26 19:00, and in current snapshots: computed from the isolated margin in the exchange-state snapshot, when the position had no fills and no funding settlement since then; a `null` price means no positive liquidation price exists |
| `not_published_cross` | A cross-margin position. Its liquidation price depends on every position and mark in the account, so it is not published |
| `changed_since_snapshot` | An isolated position that had fills between `snapshot_as_of` and the time the row describes (from 2026-09-26 19:00, also a funding settlement), so the snapshot value no longer applies |
| `unavailable` | No snapshot value applies: reconstructions, Lighter, positions no snapshot covered or opened after it, and isolated positions whose observed size did not match |

## Finality and freshness

Historical responses can report two time boundaries. They serve different purposes:

| Field | Where it appears, and what it means |
| - | - |
| `meta.built_through` | Reconstructions and change logs. History is available up to, but not including, this instant. These reads cannot go beyond it |
| `meta.finalized_through` | Reconstructions, change logs, position and account history, and summary series. Events before this instant are final. It is never later than `built_through` |

On Lighter and Robinhood Chain, `finalized_through` follows the finalized trade record and usually trails the present by about a day. Change legs after that boundary carry `finalized: false`; Lighter position rows based on those trades also remain preliminary.

Current and exact-hour account positions, current account summaries, market positions, current market summaries, and bulk pages do not carry these boundaries. Use `GET /v1/data-quality/positions` to check finality and snapshot age before a job, and [Data quality](/data-quality) for the wider window checks. A fresh snapshot is not necessarily finalized.

## Quality

Position rows and summaries carry `quality`; snapshot responses also report `meta.quality`. Change-log rows use `continuity` and `finalized` instead.

| Row `quality` | Meaning |
| - | - |
| `complete` | Every field the row should carry is present |
| `partial` | A mark or entry price is missing, so value and PnL fields are `null`, or the position is next to a gap in the venue's published data (see [Known limits](#known-limits)) |
| `degraded` | A data consistency issue affects the result; do not treat it as complete |
| `preliminary` | Lighter: built from trades that are not yet finalized |
| `unreconciled` | Lighter: a market with no finalized trade record, served from real-time data only |
| `incomplete` | Lighter: the position's history is incomplete |

`meta.quality` is `complete`, `partial`, or `degraded`. Read it together with row-level quality. A row marked `complete` does not establish that the entire requested history is available or finalized.

## Known limits

* History begins at the dates in [Historical coverage](#historical-coverage). Earlier state is unavailable, and `never_seen` applies only within that coverage.
* Recent Lighter and Robinhood Chain snapshots can report `meta.quality: "degraded"` while their rows are `preliminary` and `finalized: false`. Recheck quality and `meta.finalized_through` after finalization before using them as final historical records.
* Hyperliquid's published fill data has gaps in three short windows in June and July 2025. A small number of positions around those windows are marked `partial`.
* Lighter rows have no leverage multiple, cumulative funding, or liquidation price: those members are `null` and `liquidation_price_status` is `unavailable`. Account summaries on Lighter are position aggregates only.
* Spot markets, including Lighter spot pairs, and HIP-4 outcome markets have no positions routes. Robinhood Chain has no L1 address lookup.

## Field reference

Financial values such as sizes, prices, and PnL are decimal strings, not JSON numbers. Counts and fields such as `max_leverage` are integers. Preserve decimal precision and treat `null` as unknown, not zero.

Robinhood Chain monetary values are in USDG, including Lighter fields whose names contain `usdc`.

Times in `data` are RFC 3339 UTC strings, with fractional seconds when non-zero. Times in `meta` always include milliseconds. Request parameters (`timestamp`, `start`, `end`, `hour`) use epoch milliseconds.

### Position

| Field | Meaning |
| - | - |
| `symbol`, `coin` | Market symbol; `coin` is an alias |
| `dex` | HIP-3 dex name |
| `size`, `side` | Signed size and `long` or `short` |
| `entry_price`, `mark_price`, `mark_time` | Average entry, mark, and when the mark was taken |
| `position_value`, `unrealized_pnl` | Absolute value at the mark and unrealized PnL, in the quote currency |
| `return_on_equity` | Hyperliquid return on equity, as a fraction |
| `leverage` | `{type, value}`: `cross` or `isolated` and the leverage multiple. On a Hyperliquid or HIP-3 reconstruction `type` is `unknown` and `value` is `null`; on Lighter `type` is the margin mode and `value` is `null`. The object is always present |
| `max_leverage`, `margin_used` | Hyperliquid maximum leverage and margin in use |
| `liquidation_price`, `liquidation_price_status` | Liquidation price when published, and why it is `null` when it is not: `exact`, `changed_since_snapshot`, `not_published_cross`, or `unavailable`. See [Liquidation price status](#liquidation-price-status) |
| `cum_funding` | `{all_time, since_open, since_change}` cumulative funding, from snapshots. The object is always present; its members are `null` on Lighter and on reconstructions |
| `opened_at` | When the current position opened; `null` if it opened before coverage. In Hyperliquid and HIP-3 snapshots from 2026-09-26 19:00, also `null` when the position was already open at the exchange-state snapshot |
| `snapshot_as_of` | When the snapshot-only fields were taken; see [Timing and provenance](#timing-and-provenance) |
| `snapshot_ts` | The hour a history or bulk row describes |
| `quality` | Row quality |

Lighter rows add `account_index` (a string, because indexes can exceed JavaScript integers), `account_kind` (`user`, `settlement`, `insurance`, or `system`), `initial_margin_fraction`, `allocated_margin`, `margin_mode`, `mark_source`, and `finalized`.

### Change

Each change row describes one trade, liquidation, deleverage, or settlement leg for the account, with the position before and after it. Some Lighter legs leave the position unchanged.

Change-log responses use `meta.source: "changes"`. A leg with `continuity: "quarantined"` is excluded from position state; do not apply it as a normal fill when processing the log.

| Field | Meaning |
| - | - |
| `timestamp` | Fill time |
| `symbol`, `coin`, `dex` | Market |
| `side`, `price`, `size` | Leg side (`B` or `A`, as on trades), price, and size |
| `start_position`, `end_position` | Signed position before and after the leg |
| `entry_price_after` | Average entry after the leg; `null` when flat |
| `event_type` | `open`, `increase`, `reduce`, `close`, or `flip`; Lighter adds `settlement` and `unchanged` for legs that leave the position unchanged |
| `cause` | `trade`, `liquidation`, `liquidation_counterparty`, `adl`, `settlement`, or `unknown`. The liquidation events themselves are on [Liquidations](/rest-api/liquidations) |
| `direction`, `closed_pnl`, `crossed` | Hyperliquid direction label, realized PnL, and taker flag |
| `realized_pnl`, `is_maker` | Lighter realized PnL and maker flag |
| `fee`, `fee_token` | Fee and its asset |
| `trade_id`, `order_id` | Identifiers that join to trades |
| `opened_at` | Start of the position lifecycle the leg belongs to; `null` if it began before coverage |
| `seq`, `block_number`, `event_index` | Hyperliquid ordering; block fields appear only where block context exists |
| `continuity` | `ok`, `first_seen`, `inferred`, or `quarantined` |
| `finalized` | Whether the leg is final |

Lighter change rows add `position_size_before` and `position_size_after`, `fee_rate`, `fee_usdc`, and `usdc_amount`. On Robinhood Chain, `fee_token` is `USDG` and the USDC-named amount fields are in USDG.

### Account summary

On an account positions response, `data.account` contains a summary only on the first page of an eligible snapshot read:

| Venue | When `data.account` contains a summary |
| - | - |
| Hyperliquid core | The snapshot has a summary for the wallet |
| HIP-3 | `dex` or `symbol` scopes the request to one dex |
| Lighter, either deployment | The request has no `symbol` filter |

Otherwise, including on later pages and reconstructed reads, `data.account` is `null`.

All account summaries carry `total_position_value`, `total_unrealized_pnl`, `long_value`, `short_value`, `n_positions`, and `quality`. A total with any unpriced position is `null`, never a partial sum.

Hyperliquid and HIP-3 also carry `account_value`, `cross_account_value`, `collateral`, `total_margin_used`, `cross_maintenance_margin_used`, `withdrawable`, `account_mode`, and `snapshot_as_of`. Balances describe `snapshot_as_of`, not `meta.as_of`; see [Timing and provenance](#timing-and-provenance). `withdrawable` is `null` except in Hyperliquid core hourly snapshots up to 2026-09-26 18:00 that captured it. HIP-3 summaries are scoped to one dex.

Lighter summaries identify the account with `account_index` and contain position aggregates only, not account balances.

### Market rows and summaries

Market and bulk rows carry the account key (`user_address` on Hyperliquid, `account_index` and `account_kind` on Lighter), `symbol`, `size`, `side`, `entry_price`, `mark_price`, `position_value`, `unrealized_pnl`, `leverage_type`, `liquidation_price`, and `quality`. The first page of `/positions/{symbol}` adds `meta.totals` for the whole filtered set.

Market summaries carry `long_count`, `short_count`, `long_size`, `short_size`, `long_value`, `short_value`, `long_avg_entry_price`, `short_avg_entry_price`, `long_positions_with_entry`, `short_positions_with_entry`, `long_top10_value_share`, `short_top10_value_share`, `top10_value_share`, and `quality`.

## Parameters

| Parameter | Routes, and meaning |
| - | - |
| `timestamp` | Account positions. As-of instant in epoch milliseconds or RFC 3339; omit for the latest live snapshot |
| `start`, `end` | History, change log, account history, summary series. Window `[start, end)` in epoch milliseconds or RFC 3339; defaults to the 24 hours before `end`, and `end` defaults to now |
| `symbol` | Account routes except account summaries. One market. On Lighter and Robinhood Chain, pass the symbol exactly as `/instruments` lists it, in uppercase such as `BTC`; HIP-3 symbols keep their lowercase dex prefix, such as `xyz:TSLA` |
| `dex` | HIP-3 account routes. One dex; other venues reject it |
| `hour` | Market positions, bulk. An exact UTC hour in epoch milliseconds; market positions default to the latest live snapshot, bulk to the latest hourly snapshot |
| `side` | Market positions. `long` or `short` |
| `min_value` | Market positions. Minimum position value |
| `include_system` | Lighter market, summary, and bulk routes. Include settlement, insurance, and system accounts, which are excluded by default |
| `limit`, `cursor` | Paginated routes. Page size and the `meta.next_cursor` of the previous page |
| `format` | Bulk. `arrow` for Apache Arrow |

| Route type | Default and maximum `limit` |
| - | - |
| Paginated account routes | 500 by default, up to 5,000 |
| Market routes | 100 by default, up to 2,000 |
| Summary series | 100 hours by default, up to 168 hours per page |
| Bulk, JSON | 1,000 by default, up to 2,000 |
| Bulk, Arrow | 1,000 by default, up to 50,000 |

Market summary routes accept a `limit` parameter up to 2,000, but a history page contains at most 168 hourly points. Follow the returned cursor for the remaining hours.

## Pagination

Pass `meta.next_cursor` back as `cursor`, keeping the route and all other parameters unchanged. Stop when no next cursor is returned. Treat cursors as opaque values; do not decode or modify them. They expire after 24 hours. Reusing a cursor with different parameters returns `invalid_cursor`.

Market and bulk pages use the same snapshot throughout pagination. If that snapshot is no longer available, the next page returns `409` with `snapshot_advanced`. Restart from the first page without a cursor; do not combine pages from the old and new snapshots.

History pages can end on an hour boundary with fewer than `limit` rows and still have a next cursor. A short page does not mean you have reached the end.

## Bulk export in Arrow

Request Arrow on a bulk route with `format=arrow` or `Accept: application/vnd.apache.arrow.stream`. An Arrow response is an Apache Arrow IPC stream with up to 50,000 rows per page. It has no JSON envelope: read `x-count`, `x-next-cursor`, and `x-schema-version` from the response headers. The schema version is `positions.v1`.

Always check the response `Content-Type` before parsing. The route returns JSON if Arrow output is not enabled, even when you requested Arrow.

## Market-wide positioning

```bash theme={"theme":"github-dark"}
curl "https://api.0xarchive.io/v1/hyperliquid/positions/BTC?side=long&min_value=100000&limit=50" \
  -H "X-API-Key: $OXARCHIVE_API_KEY"
```

Rows are sorted by position value, largest first. On Lighter, pass `include_system=true` to include settlement, insurance, and other system accounts. `/positions/{symbol}/summary` without `start` or `end` returns one summary for the latest snapshot; with a window it returns one summary per available hour.

## Lighter L1 address lookup

Set `WALLET_ADDRESS` to the L1 address you want to look up:

```bash theme={"theme":"github-dark"}
curl --get "https://api.0xarchive.io/v1/lighter/accounts" \
  --data-urlencode "l1_address=${WALLET_ADDRESS}" \
  -H "X-API-Key: $OXARCHIVE_API_KEY"
```

`data` holds `l1_address`, `total_accounts`, and `accounts`, each with `account_index`, `account_type`, and `first_seen`. The lookup covers Lighter mainnet only.

## SDKs

Positions resources are available in SDK version `1.12.0` and later for Python, TypeScript, and Rust. The examples below use `OXARCHIVE_API_KEY` and `WALLET_ADDRESS` from your environment. See [SDK installation](/sdks/installation) and [REST usage](/sdks/rest-usage) for setup and language-specific method names.

<CodeGroup>
  ```python Python theme={"theme":"github-dark"}
  # pip install oxarchive==1.12.0
  from os import environ
  from oxarchive import Client

  client = Client(api_key=environ["OXARCHIVE_API_KEY"])
  wallet = client.hyperliquid.positions.get(environ["WALLET_ADDRESS"])
  for position in wallet.data.positions:
      print(position.symbol, position.side, position.size)

  print(wallet.meta.as_of)
  ```

  ```typescript TypeScript theme={"theme":"github-dark"}
  // npm install --save-exact @0xarchive/sdk@1.12.0
  import { OxArchive } from "@0xarchive/sdk";

  const client = new OxArchive({ apiKey: process.env.OXARCHIVE_API_KEY! });

  const wallet = await client.hyperliquid.positions.get(process.env.WALLET_ADDRESS!);
  console.log(wallet.data.positions.length, wallet.meta.asOf);

  const summary = await client.lighter.positions.marketSummary("BTC");
  console.log(summary.data);
  ```

  ```rust Rust theme={"theme":"github-dark"}
  // cargo add oxarchive@=1.12.0
  use oxarchive::OxArchive;

  #[tokio::main]
  async fn main() -> oxarchive::Result<()> {
      let api_key = std::env::var("OXARCHIVE_API_KEY").expect("Set OXARCHIVE_API_KEY");
      let client = OxArchive::new(api_key)?;
      let address = std::env::var("WALLET_ADDRESS").expect("Set WALLET_ADDRESS");
      let wallet = client
          .hyperliquid
          .positions
          .get(&address, None)
          .await?;
      println!("{} positions", wallet.data.positions.len());
      Ok(())
  }
  ```
</CodeGroup>

## Billing and caching

Position routes cost one credit per 1,000 rows returned, with a minimum of one credit per request. Account summary routes and the Lighter L1 lookup cost one credit per request. Your plan's history window applies to `timestamp`, `hour`, and `[start, end)`.

When caching results, retain their timestamps, quality, and finality metadata. A cached current response still describes its original `meta.as_of`, and preliminary history should be refreshed after finalization.

## Errors

Errors use the standard error body with `code`, `error`, `param` when a parameter is at fault, and an `error_code` for the cases below.

| Status | When |
| - | - |
| `400` | Malformed address or account index, `hour` not an exact UTC hour, `dex` outside HIP-3, `include_system` outside Lighter, an unknown symbol, or `start` not before `end`. A cursor that does not match the request returns `invalid_cursor` |
| `404` | No hourly snapshot exists for the requested `hour`, or the symbol is not a perpetual market of that Lighter deployment |
| `409` | `snapshot_advanced`: the snapshot a market or bulk cursor was paging has been replaced or has expired. Restart without a cursor |
| `503` | `positions_unavailable` or `pagination_unavailable` while the service recovers, or `reconstruction_unavailable` when an as-of instant cannot be rebuilt right now. Retry, or ask for an exact UTC hour |

## Common mistakes

* Treating `data.account_seen: "never_seen"` as evidence that an account never traded. It is scoped to covered history.
* Reading the `null` members of a reconstructed row as zero, or typing `leverage` as nullable and `leverage.type` as only `cross` or `isolated`. A reconstruction keeps the `leverage` and `cum_funding` objects, with `leverage.type` set to `unknown` on Hyperliquid and HIP-3, and its `null` values mean unknown, not zero.
* Joining Lighter account indexes across mainnet and Robinhood Chain. They are separate account spaces.
* Paging with changed parameters. A cursor belongs to the exact request that issued it.

## Access

Each venue's start date, WebSocket channels, and page limit come from [`GET /v1/capabilities`](/capabilities), followed by the SDK, CLI, and MCP names for the same data. The start date is the first served row on that venue; exact starts vary by market, so check `/v1/symbols` for a single symbol. Send `0xArchive-Version: 2026-10-01` for the current response contract; see [API conventions](/conventions).

| Venue | Availability |
| - | - |
| Hyperliquid | No venue-wide start date; see `/v1/symbols` per symbol<br />REST only, no WebSocket channel<br />No fixed page limit<br />Page limits: 5,000 rows on wallet/account histories, 2,000 on market listings. |
| HIP-3 | No venue-wide start date; see `/v1/symbols` per symbol<br />REST only, no WebSocket channel<br />No fixed page limit<br />Page limits: 5,000 rows on wallet/account histories, 2,000 on market listings. |
| Lighter | No venue-wide start date; see `/v1/symbols` per symbol<br />REST only, no WebSocket channel<br />No fixed page limit<br />Page limits: 5,000 rows on wallet/account histories, 2,000 on market listings. |
| Lighter on Robinhood Chain | No venue-wide start date; see `/v1/symbols` per symbol<br />REST only, no WebSocket channel<br />No fixed page limit<br />Page limits: 5,000 rows on wallet/account histories, 2,000 on market listings. |

| Surface | Names |
| - | - |
| Python SDK | `client.hyperliquid.positions.get(address)` and the other `positions` methods on the Hyperliquid, HIP-3, Lighter, and Robinhood Chain clients |
| TypeScript SDK | `client.hyperliquid.positions.get(address)` and the other `positions` methods on the Hyperliquid, HIP-3, Lighter, and Robinhood Chain clients |
| Rust SDK | `client.hyperliquid.positions.get` and the other `positions` methods on each venue client |
| CLI | `oxa positions get`, `history`, `changes`, `account`, `account-history`, `market`, `summary`, `all` |
| MCP tools | `get_account_positions`, `get_account_positions_history`, `get_account_position_changes`, `get_account_summary`, `get_account_summary_history`, `get_market_positions`, `get_market_positions_summary`, `get_lighter_accounts` (account tools take `venue`) |

## Next step

For the fills behind each position change, continue to [Trades](/rest-api/trades).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.