{ code, tool?, message }: a numeric code, the originating tool (provider) when one produced the error, and a human-readable message. The SDK throws it as a PerpsError.
PerpsError class, which carries the numeric error code. Wallet errors, AbortError, and WebSocket-channel errors are not PerpsError; detect a user rejection through EIP-1193 code 4001 or UserRejectedRequestError along the cause chain.
Discriminating errors
The error model is flat: there is one class —PerpsError — and you branch on its numeric .code (a PerpsErrorCode). There are no error subclasses to instanceof. A non-2xx API response is re-hydrated into a PerpsError carrying the backend’s code and message; error.tool names the originating provider when the error came from one, and is the string "unknown" for backend errors that carry no provider.
SDK-originated errors (for example a missing agent key) carry canonical messages from the
PerpsErrorMessage enum. A missing or corrupt local agent key is SDKError (2005) with PerpsErrorMessage.AgentNotFound or AgentStorageCorrupt; compare error.message.
A venue refusal inside a 202 action response does not throw for the execution methods. The refusal appears as a result with status Failed, with error and, when known, errorCode. An unclear venue answer appears as Indeterminate. The HTTP status, the action status and the errorCode are three separate axes. Check each result as well as catching thrown errors.
Account access errors
A data read such asgetPositions or getOrders throws the account’s access state as one of three codes: SetupRequired (no credential is stored), Unauthorized (the venue rejected the credential), or AccountNotFound (the wallet has no venue account). A status read such as checkSetup returns the state as a value. getAccount is provider-specific: Hyperliquid treats it as a data read, while Ondo and Lighter expose status behavior subject to Lighter’s documented exceptions. See Account access for the rule, the per-provider table, and what to render for each code.
Error Code Ranges
Error codes use the 2000+ range to avoid collision with the main LI.FI API (which uses 1000+):Error Code Reference
Base Errors (2000-2009)
Authentication Errors (2010-2019)
Trading Errors (2020-2039)
Nonce Errors (2040-2049)
Payload Errors (2050-2059)
Routing Errors (2060-2069)
Setup Errors (2070-2079)
Capability Errors (2080-2089)
Rate-Limit Errors (2090-2099)
SDK Error Code Constants
The SDK exportsPerpsErrorCode for type-safe error handling:
Resolving a code from an HTTP status
The SDK exportserrorCodeFromStatus to resolve the PerpsErrorCode a non-ok HTTP response carries. The Hyperliquid and Ondo provider boundaries and the LI.FI transport layer call it to build the code on a PerpsError:
- A
429status always resolves toRateLimitExceeded. No per-boundary mapping can override it. - Every other status resolves to a per-boundary
statusCodesoverride when one is given (for example Hyperliquid maps401toUnauthorizedand403toAgentUnauthorizedon its/inforeads), else it resolves to thefallbackcode passed in.
errorCodeFromStatus. It signals rate limits with 429 or 405, and the SDK holds all Lighter requests until Retry-After expires (capped at 5 minutes, 60 seconds when the header is missing or invalid), throwing RateLimitExceeded meanwhile. Other Lighter body codes map through the SDK’s own table and default to ThirdPartyError.
Retryability
Troubleshooting
ExchangeRejected (2020)
The provider backend rejected the request. Check:- Order parameters are valid (size within limits, price within bounds)
- The market is active and not in maintenance
- Request body matches the expected schema
- On Ondo, this is also the fallback for any venue error code the backend does not classify more specifically (the venue’s own error detail is included), and the specific mapping for
too_many_twap_orders
InsufficientMargin (2021)
Not enough margin for the requested order. Check:- Account has sufficient USDC balance
- Existing positions don’t consume too much margin
- Reduce leverage or order size
- On Lighter, this also covers the account reporting below maintenance or initial margin, or too little margin to create the order
- On Ondo, this also covers an SDK-side read reporting
insufficient_margin. A write relayed through the backend that reports it returnsExchangeRejected(2020)
InsufficientBalance (2022)
Account lacks funds for the operation. Check:- Deposit sufficient funds to your account
- Check balances with
getAccount() - On Lighter, this also covers the venue reporting insufficient collateral, or insufficient asset balance to cover the order or its fee
SignatureInvalid (2010)
The signature could not be verified. Check:- The correct wallet/agent signed the
typedData - The
typedDatawas not modified after creation - The nonce has not expired (re-call
createActionto get fresh data) - On Ondo, this also covers an HMAC signature the venue rejected as malformed or invalid — verify the request body was not re-serialized after signing, since the signature covers the exact byte string
AgentUnauthorized (2011)
A Hyperliquid/info read was rejected with a 403. Check:
- The address and agent match an account the venue recognises
- Re-run the setup flow (via
checkSetupandexecuteProviderSetupAction) if the agent was never approved
SDKError (2005) with PerpsErrorMessage.AgentNotFound or AgentStorageCorrupt. A venue rejection of an unapproved agent at order submission surfaces as ExchangeRejected (2020).
TermsNotAccepted (2012)
The address has not accepted the current terms of service. Check:- Fetch the current terms and acceptance status with
GET /v1/perps/meta/terms?address= - Have the address accept the current
termsVersionbefore performing an action
Unauthorized (2013)
The caller credential was rejected. Check:- The
x-lifi-api-keyvalue is a valid, currently-active API key - The API key is linked to an integration in the LI.FI Partner Portal
- On Hyperliquid, this also covers a
/inforead that the venue rejected with a 401 - On Lighter and Ondo, every
Unauthorizedfrom a read means the venue refused the credential you presented.SetupRequiredmeans that no credential was stored. Lighter reports a rejected token and a revoked token through the same code, so the SDK cannot distinguish the two. Re-provision the Lighter auth token, or runsiweLoginagain on Ondo. See Account access - On Ondo, this also covers the venue rejecting the client-held API key (not found, or missing the required scope) on an HMAC-signed trading action, and an HTTP 401 on any request — a 401 outranks any other body code the venue returns, so a rejected session still evicts the stored session credential. The SDK evicts a key or session the venue reports as unauthorized and creates a fresh one on the next trading action — re-run the failed action
MarketNotFound (2023)
The market doesn’t exist on the specified provider. Check:- Use
getMarkets()to list validmarketIdvalues for the provider getMarketalso throws this code when the market exists but is not activegetQuotethrows this code when no market matchessymbolandtype, or when the provider returned no live priceGET /marketsandGET /marketsContextreturn it whenmarketIdsmatches no market
OrderNotFound (2024)
The order ID doesn’t exist. Check:- Use the correct
orderId(from the submit response) - The order may have already been fully filled or cancelled
- Verify the
providerparameter matches where the order was placed - On Ondo, this also covers a
twapIdthe venue no longer recognizes forcancelTwapOrder
PositionNotFound (2025)
Reserved. The code is defined, but no provider currently returns it.AccountNotFound (2026)
The address has not been onboarded on the specified provider. Check:- For Hyperliquid, the first deposit creates the account; offer
getDepositFlow. Account data reads and account-channel subscriptions run thepreTransferCheckprobe, then throw this code before reading account data or opening a venue subscription - For Lighter, the first deposit creates the account. Applicable data reads throw this code for a wallet with no Lighter account; credential and setup checks can take precedence. See Account access
- For Ondo, a missing or expired session does not raise this code:
getAccountreturns a logged-out account instead - The
providerparameter matches a provider where the user actually holds an account
InvalidNonce (2040)
The nonce in the request failed validation. Check:- The
typedDatawas not modified after creation - Re-call the create endpoint to get fresh payloads
- On Lighter, this also covers the venue reporting an invalid nonce, or a batch transaction whose nonce does not increase
NonceAlreadyUsed (2041)
The nonce has already been consumed by a previous request. This typically happens when:- Submitting the same signed payload twice
- Re-submitting after a successful operation
- On Ondo, replaying a
placeOrderrequest — the SDK generates a freshclientOrderIdper request, and the venue rejects a reused one; the venue reports this as aclientOrderID_collision
NonceExpired (2042)
The nonce has exceeded its time-to-live. Payloads expire after a short period for security. Check:- Sign and submit payloads promptly after creation
- Re-call the create endpoint to get fresh payloads
PayloadMismatch (2050)
The signed payload doesn’t match what was stored server-side. This can happen if:- The
typedDatawas modified before signing - A different payload was substituted
typedData returned from the create endpoint without modification.
SetupRequired (2070)
A recoverable provider setup action must be completed before the operation can be retried. Check:- Re-run
checkSetupto get the current required setup action - Complete the returned setup action, then resubmit the original request
- For Lighter integrator fee approval, this re-emits the
approveIntegratoraction for the current recipient and per-transaction-type rates - Lighter accepts integrator approvals and integrator-routed orders only from Plus or Premium accounts (venue code
21520). The SDK reports it asSetupRequired. Re-runcheckSetup, execute theaccountTypestep (Plus or Premium), then retry - For Ondo, this covers a withdrawal destination that is not yet in the Ondo address book (
withdrawal_address_not_found) — runaddWithdrawalAddress, then request a new withdrawal plan - For Hyperliquid, this covers a builder-fee approval the venue reports as missing — complete
approveBuilderFee - For Lighter and Ondo, a data read with no stored credential throws this code, and the SDK sends no request to the venue. Run the setup action with
gatesAccountReads: true:registerApiKeyon Lighter,siweLoginon Ondo. See Account access - Meta referral endpoints return it for a code that is unavailable, already owned, or already attached — pick another code; re-running
checkSetupdoes not help
FeatureUnavailable (2080)
The venue does not offer the requested capability. Check:- The feature is not available on this provider — no request change or setup step makes it succeed
- Consult the provider’s page for the capabilities it supports before offering the entry point
- Ondo has no
sendAsset,modifyOrder, orupdatePositionMarginmethods and no choice setup step; the venue also disables specific features at times, surfaced through this same code
RateLimitExceeded (2090)
The caller spent its request budget for the current one-minute window. Check:- Wait for the current window to reset, then retry
- An API key with a negotiated rate limit gets a higher budget than the default. See Rate Limits
- A 429 from the venue itself also raises this code — no per-boundary mapping can override it. This covers a Hyperliquid
/inforead and an Ondo request that returned 429
ServerError (2001)
Either the request failed before any response (SDK-side network failure such as a thrown fetch, DNS failure, or connection reset), or the backend returned HTTP 500 for an internal fault. Check:- Network connectivity between the SDK and the LI.FI API or the provider
- Retry after a short delay — an idempotent read is safe to retry, a mutation is not, since the request may or may not have reached the venue
ThirdPartyError (2004)
The venue answered with a failure that no other code describes, so the outcome of the request stays open — the venue received the request and rejected it for a reason the SDK does not classify more specifically. Check:- An execution result with this code has
statusIndeterminatewhen the venue gave a transport failure, a 5xx answer or an unreadable body - Provider may be experiencing issues
- Check provider status pages for outages
- Retry an idempotent read after a short delay. Do not blindly retry a mutation: since the venue answered, a blind retry risks a duplicate action if the original request actually succeeded venue-side
TimeoutError (2003)
An action was submitted but its outcome could not be confirmed in time (server-side verification timeout). The action may still have landed. An execution result with this code hasstatus Indeterminate. Check:
- Read order and position state before retrying
- Do not blindly re-submit the action
ValidationError (2002)
The request failed validation, on the backend or in the SDK. Check:- The parameters and decimals are within the allowed ranges; the message says which one is not
SDKError (2005)
A client-side precondition failed, for example a provider that is not registered or a setup that is incomplete. The message identifies it. This code is raised in the SDK and never appears on the wire.RouteNotFound (2060)
The HTTP method or path does not exist. The body readsRoute <METHOD> <url> not found and carries no tool.