> ## Documentation Index
> Fetch the complete documentation index at: https://public-perps-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Account access

> Which account reads return the account's access state, which reads throw it, and the error code for each fact

The SDK reports the access state of an account in one of two ways. A **status read** returns the state as a value. A **data read** throws the state as a `PerpsError`. An empty list from a data read means only one fact: the account exists and has no rows.

## Status reads and data reads

| Kind | Reads | The account's access state |
| - | - | - |
| Status read | `checkSetup`, `accountExists`, `getDepositFlow`, `getWithdrawFlow`, plus Ondo and Lighter `getAccount` | Returned as a value. Lighter `getAccount` has two exceptions, listed below this table. |
| Data read | `getPositions`, `getOrders`, `getOrder`, `getFills`, `getActivity`, `getPortfolioHistory`, `getWithdrawableBalances`, `getAvailableToTrade`, `getMarketSettings`, Hyperliquid `getAccount`, and WebSocket `subscribe` on an account channel | Thrown as a `PerpsError` |

A status read tells you what the user must do next:

* Lighter `getAccount` returns a zero fee tier and `apiKeyRegistered: false` when the wallet has no locally stored API key that matches the registered key. Otherwise it reads the fee tier with the resolved token and throws `Unauthorized` if Lighter rejects the token. `getAvailableToTrade` reads through `getAccount`, so it throws the same `Unauthorized`.
* Ondo `getAccount` returns an empty account without a session, and Ondo `accountExists` returns `false`.
* Ondo `getDepositFlow` and `getWithdrawFlow` return `kind: 'setupRequired'` without a session.
* Hyperliquid `accountExists` returns `false` before the wallet's first deposit, while `getDepositFlow` remains available.

Lighter `getAccount` throws in two cases:

* `AccountNotFound` for a wallet with no Lighter account. Call `accountExists` before `getAccount`.
* `Unauthorized` when an auth token resolves for the registered API key and Lighter rejects the token on the fee-tier read.

## Fact to code

Each data read throws one code per fact:

| Fact | Code | What to render |
| - | - | - |
| No credential is stored. The setup step with `gatesAccountReads: true` is unsatisfied. The SDK sends no request to the venue. | `SetupRequired` (2070) | Offer that setup action: `registerApiKey` on Lighter, `siweLogin` on Ondo. Do not offer a retry. On Lighter, read `accountExists` first. See [Before and after a read](#before-and-after-a-read). |
| The SDK sent a credential, and the venue rejected it. The credential is invalid, revoked, or expired. | `Unauthorized` (2013) | Tell the user that the venue refused the credential. Ask the user to authenticate again: run `siweLogin` on Ondo, or re-provision the auth token on Lighter. |
| The wallet has no account at the venue. | `AccountNotFound` (2026) | Offer onboarding. Read the deposit route from `getDepositFlow`. |
| The account exists and has no rows. | No error. The read returns an empty list. | Show the empty state. |

```typescript theme={null}
import { getPositions, PerpsError, PerpsErrorCode } from '@lifi/perps-sdk';

try {
  const { positions } = await getPositions(client, {
    provider: 'ondo',
    address: userAddress,
  });
  // An empty `positions` list means the account has no open positions.
} catch (error) {
  if (!(error instanceof PerpsError)) throw error;
  switch (error.code) {
    case PerpsErrorCode.SetupRequired:
      // Offer the setup step with `gatesAccountReads: true`.
      break;
    case PerpsErrorCode.Unauthorized:
      // The venue refused the credential. Ask the user to authenticate again.
      break;
    case PerpsErrorCode.AccountNotFound:
      // The wallet has no venue account. Offer onboarding.
      break;
    default:
      throw error;
  }
}
```

## Before and after a read

`checkSetup()` tells you before a read what the read will throw. The thrown code tells you after the read. Both report the same fact, with the Lighter token-ordering exception that follows the table. A UI can use one or both.

| `checkSetup()` result, before the read | Code, after the read |
| - | - |
| A `checklist` item has `descriptor.gatesAccountReads === true` and `satisfied: false` | `SetupRequired` from each read that needs the credential |
| `accountExists === false` | `AccountNotFound` from every Hyperliquid account data read and each Lighter read that needs no credential. A Lighter read that needs a token throws `SetupRequired` when no token resolves, because it checks the token first. |

For a Lighter wallet with no account, `checkSetup()` returns an empty `setup` list and an empty `checklist`, so it has no `registerApiKey` step to offer. Treat the missing account as the first fact: offer onboarding, also when a Lighter read throws `SetupRequired`.

`checkSetup()` cannot predict `Unauthorized`, because the venue decides it when it receives the credential. On Ondo, the provider removes the rejected session token, so the next `checkSetup()` shows `siweLogin` as unsatisfied.

`checkSetup()` is not a pure read. Call it once after each setup step, not before each data read. See [checkSetup](/sdk/trading/methods#checksetup).

## Per provider

### Lighter

* `getOrders`, `getOrder`, `getFills`, `getActivity` and `getPortfolioHistory` need an auth token. They throw `SetupRequired` when no token resolves, `Unauthorized` when Lighter rejects the token, and `AccountNotFound` when the wallet has no Lighter account.
* These reads check the token before the account. A wallet with no Lighter account and no token gets `SetupRequired`.
* When Lighter reports an SDK-owned read-only token as revoked, the SDK replaces the token and retries the read once. If Lighter rejects the new token, the read throws `Unauthorized`.
* `getPositions` and `getWithdrawableBalances` need no token. Their only access code is `AccountNotFound`.
* `getAvailableToTrade` reads through `getAccount` on every Lighter market. It throws `AccountNotFound` for a wallet with no Lighter account, and `Unauthorized` when an auth token resolves for the registered API key and Lighter rejects the token.
* `getMarketSettings` needs no token. It returns the venue default for a wallet with no Lighter account.
* `getOrders` with `statuses: []` returns an empty list without a token. It sends no request to Lighter.
* The WebSocket `orderUpdates` and `positions` channels throw `SetupRequired` at subscribe time when no token resolves. When no auth-token resolver is wired, they throw `SDKError` — a wiring defect, not an account state, so no account setup fixes it. See [Lighter WebSocket](/providers/lighter/websocket#authenticated-channels).
* `orderUpdates`, `fills`, `positions` and `accountSummary` each resolve the Lighter account index before the channel opens, so every one throws `AccountNotFound` at subscribe time for a wallet with no Lighter account — including `fills` and `accountSummary`, which need no token. `accountSummary` degrades to the public `account_all` variant instead of throwing when no token resolves.

See [Lighter / Authentication](/providers/lighter#authentication-and-rate-limits) for the token resolution order.

### Ondo

* `getPositions`, `getOrders`, `getOrder`, `getFills`, `getActivity`, `getPortfolioHistory`, `getWithdrawableBalances`, `getAvailableToTrade` and `getMarketSettings` need a session. They throw `SetupRequired` when no session token is stored, and the provider sends no request to Ondo.
* When Ondo rejects the stored session token, the provider removes the token and that read throws `Unauthorized`. The next read throws `SetupRequired`.
* Ondo opens the account at the SIWE login, so an Ondo data read does not throw `AccountNotFound`.
* `getOrders` with `statuses: []` also throws `SetupRequired` without a session.
* The WebSocket account channels throw `SetupRequired` at subscribe time when no session is stored.

### Hyperliquid

* Hyperliquid account reads need no credential, so they do not throw `SetupRequired`.
* `getAccount`, `getPositions`, `getOrders`, `getOrder`, `getFills`, `getActivity`, `getPortfolioHistory`, `getWithdrawableBalances`, `getAvailableToTrade` and `getMarketSettings` first check `preTransferCheck.userExists`. They throw `AccountNotFound` before reading account data when it is `false`.
* `getOrders` with `statuses: []` still performs the account check before returning an empty list.
* `orderUpdates`, `fills`, `positions`, `spotBalances`, `accountSummary` and `availableToTrade` WebSocket subscriptions throw `AccountNotFound` before opening a venue channel.
* `accountExists` remains a boolean status read, and `getDepositFlow` remains available before account creation.
* A Hyperliquid `/info` read that the venue rejects with HTTP 401 throws `Unauthorized`.

See [Error Codes](/error-codes) for every code.


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