PerpsError with a numeric code, the originating tool (provider), and a human-readable message.
PerpsError class, which carries the numeric error code:
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.
SDK-originated errors (for example a missing agent key) carry canonical messages from the
PerpsErrorMessage enum.
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 provider boundaries (Hyperliquid, Lighter, Ondo) and the LI.FI transport layer all 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.
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 the venue envelope reporting
insufficient_margin
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)
The agent wallet is not approved on the provider. Check:- The setup flow completed successfully (via
checkSetupandexecuteProviderSetupAction) - The agent address matches what was authorized
- The authorization has not been revoked
- On Hyperliquid, this also covers a
/inforead that the venue rejected with a 403
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 has an integration mapping in the Partner Service
- The
x-lifi-integratorvalue, if sent, matches the identity your API key resolves to — remove it or correct it if it differs - On Hyperliquid, this also covers a
/inforead that the venue rejected with a 401 - On Lighter, every
Unauthorizedfrom a read means the same thing: re-provision the auth token. The venue reports a rejected token and a revoked token through the same code, so the SDK cannot distinguish the two - 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 symbol doesn’t exist on the specified provider. Check:- Use
getAssets()to list valid asset IDs - Asset IDs are case-sensitive (use
BTC, notbtc) - The asset hasn’t been delisted
getQuotealso throws this code when the market is a known market but the provider returned no live price for it
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)
No position exists for the specified symbol. Check:- Verify you have an open position for this symbol
- Use
getPositions()to list current positions
AccountNotFound (2026)
The address has not been onboarded on the specified provider. Check:- The user has completed the setup flow for the provider (see Hyperliquid / Setup or Ondo / Setup)
- For Lighter, an account is created on first deposit
- For Ondo, the user has completed the SIWE login — the session credential must be live
- 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 - For Ondo, this covers a missing SIWE session, an unregistered API key, or unaccepted venue terms — re-run
checkSetupto see which step is outstanding
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
withdraw,sendAsset,modifyOrder, orupdatePositionMarginmethods and no accountoptions; 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)
No answer arrived: the request itself failed before any response, for example the fetch call threw (network failure, DNS failure, connection reset). 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:- 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)
The request exceeded the timeout limit. Check:- Network connectivity
- Provider may be experiencing high latency
- Retry the operation