Skip to main content

Troubleshooting

Use this page when API reads do not match what you expect. Most issues fall into one of four buckets: invalid request parameters, stale snapshots, using the wrong surface for the job, or expecting immediate read-after-write consistency.

400 Bad Request

Both read endpoints and order preparation return 400 for requests that can't be accepted.

Common read cases:

  • /orders and /positions require a valid account address.
  • /markets/tickers validates addresses and symbols filters.
  • /markets/trading-capacity requires a valid symbol and a direction of long or short.
  • /rates validates period and averageBy.
  • /apy, /yield/gm-pools, and /performance/* validate their filters. /yield/gm-pools rejects malformed pool addresses.

POST /orders/txns/prepare can also return an application error:

  • INSUFFICIENT_LIQUIDITY means a Market Increase exceeds current JIT-aware trading capacity. Reduce the requested size or refresh capacity before preparing again. A resting increase can return the same code in validationWarnings instead because liquidity will be checked again when it triggers.
  • INSUFFICIENT_COLLATERAL means collateral remaining after fees would be below the on-chain minimum. Add collateral or change the order. For TWAP increases, every part must satisfy the minimum.

validationWarnings are non-blocking, but clients need to surface them before signing. If you receive a blocking 400, validate the query or change the order inputs before retrying; repeating the unchanged request won't succeed.

A value looks stale

Start by checking whether you are reading from a cached or indexed surface.

  • Use Oracle API /markets/info when you need near-live market state.
  • Use Oracle API /markets when a 10 second cache window is acceptable.
  • Use GraphQL for historical activity, not for the latest write-path state.
  • Use a single composite read where possible instead of stitching unrelated polls together.

For GMX API market values, inspect each /v1/markets/values row's updatedAt field. It is a Unix timestamp in milliseconds and uses the older timestamp from the main market-values refresh and the virtualInventoryForPositionsInTokens refresh. A null value means at least one component has no successful refresh timestamp.

The default backend pulls market values every 5 seconds and marks a row stale for monitoring after 15 seconds. This threshold does not evict the row:

  • A total pull failure preserves the previous values and timestamps, which continue to age.
  • A market-specific failed refresh can preserve the previous values while setting updatedAt to null.
  • Disabled markets are excluded from backend values-staleness accounting.

Compare updatedAt with a freshness limit appropriate for your application. Don't treat an HTTP 200 response as proof that every value came from the latest pull.

If you need one coherent account snapshot, prefer one positions call with includeRelatedOrders: true over multiple loosely coordinated reads.

A write succeeded, but the API still does not show it

That is usually a surface mismatch or an operation that hasn't reached a final state.

  • SDK v2 and GMX API /orders/txns/* endpoints prepare, submit, and track signed order intents. Persist their requestId and poll order status.
  • The low-level GMX Relay surface accepts already-built relay-router calldata through POST /relay/submit. Persist the returned taskId and poll it through POST /relay/status.
  • SDK v1 and direct contracts submit transactions from your wallet or backend RPC client. Wait for the transaction receipt, then poll the read surface you care about.

Generic relay statuses are pending, executed, reverted, failed, and unknown. Treat unknown as inconclusive rather than failed: keep the operation pending until your own timeout, and don't resubmit the same signed calldata blindly. A 404 immediately after an accepted submission can also be transient while status becomes readable.

For a reverted relay operation, inspect reason and the top-level revertData when present. revertData contains public on-chain revert bytes and can be decoded by the client. If submission fails before returning a taskId, capture the X-Trace-Id response header; it may be the only support identifier.

Don't assume the first follow-up HTTP or GraphQL read will reflect final state. Relay processing, transaction inclusion, keeper execution, and indexing can complete at different times.

Positions and orders do not line up

If your UI shows position state together with linked orders, fetch them from the same logical snapshot.

Recommended pattern:

import { GmxApiSdk } from "@gmx-io/sdk/v2";

const apiSdk = new GmxApiSdk({ chainId: 42161 });

const positions = await apiSdk.fetchPositionsInfo({
address: "0x9f7198eb1b9Ccc0Eb7A07eD228d8FbC12963ea33",
includeRelatedOrders: true,
});

Use a separate account-wide orders read only when you need a dedicated orders screen.

There is no documented public SLA

The current manual docs do not publish a public SLA for these surfaces. Build your client as if network errors, timeouts, or stale snapshots can happen.

Recommended client behavior:

  1. Use endpoint-specific fallback URLs where they are documented.
  2. Add retries with backoff for safe read operations.
  3. Keep write confirmation logic separate from read polling logic.
  4. Log the exact surface you queried so you can distinguish GMX API, Oracle API, GraphQL, and SDK-backed reads during incident review.
  5. Capture the X-Trace-Id response header for GMX API failures. It is exposed to browser clients through CORS; include it with any requestId or taskId in support reports.

Next steps