Skip to main content
All API errors return a PerpsError with a numeric code, the originating tool (provider), and a human-readable message.
In the SDK, every error is an instance of the single 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)

SDK Error Code Constants

The SDK exports PerpsErrorCode for type-safe error handling:

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

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

InsufficientBalance (2022)

Account lacks funds for the operation. Check:
  • Deposit sufficient funds to your account
  • Check balances with getAccount()

SignatureInvalid (2010)

The signature could not be verified. Check:
  • The correct wallet/agent signed the typedData
  • The typedData was not modified after creation
  • The nonce has not expired (re-call createAction to get fresh data)

AgentUnauthorized (2011)

The agent wallet is not approved on the provider. Check:
  • The setup flow completed successfully (via checkSetup and executeProviderSetupAction)
  • The agent address matches what was authorized
  • The authorization has not been revoked

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 termsVersion before performing an 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, not btc)
  • The asset hasn’t been delisted

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 provider parameter matches where the order was placed

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)
  • For Lighter, an account is created on first deposit
  • The provider parameter matches a provider where the user actually holds an account

InvalidNonce (2040)

The nonce in the request failed validation. Check:
  • The typedData was not modified after creation
  • Re-call the create endpoint to get fresh payloads

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
Solution: Call the create endpoint again to get new payloads with fresh nonces.

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 typedData was modified before signing
  • A different payload was substituted
Solution: Use the exact typedData returned from the create endpoint without modification.

ThirdPartyError (2004)

An external service (typically the provider) returned an error. Check:
  • Provider may be experiencing issues
  • Retry after a short delay
  • Check provider status pages for outages

TimeoutError (2003)

The request exceeded the timeout limit. Check:
  • Network connectivity
  • Provider may be experiencing high latency
  • Retry the operation