> For the complete documentation index, see [llms.txt](https://docs.native.org/native-dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.native.org/native-dev/build-with-native/native-core/reference/post-info.md).

# POST /info

All public reads use one endpoint with a top-level `type` discriminator.

A malformed body, or one whose `type` is missing or not a string, returns HTTP `400` with `error.code: "InvalidInfoRequest"`. A well-formed body whose `type` is not a known query returns HTTP `400` with `error.code: "UnsupportedInfoType"`:

```json
{
  "error": {
    "code": "UnsupportedInfoType",
    "message": "unsupported public info type `...`"
  }
}
```

### assets

```sh
curl -sS -X POST "$API_URL/info" \
  -H 'content-type: application/json' \
  -d '{"type":"assets"}'
```

Response:

See [Decimals & Units](/native-dev/build-with-native/native-core/concepts/decimals-units.md) for raw/display conversion and validation rules.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "assets": [
    {
      "asset_id": 1,
      "symbol": "USDC",
      "balance_decimals": 8,
      "withdraw_fee": "1",
      "withdraw_fee_atoms": "100000000",
      "issuer": "0x0000000000000000000000000000000000000000",
      "credit_ltv": 100,
      "credit_ltv_setting": null
    },
    {
      "asset_id": 3,
      "symbol": "ETH",
      "balance_decimals": 8,
      "withdraw_fee": "0.001",
      "withdraw_fee_atoms": "100000",
      "issuer": "0x0000000000000000000000000000000000000000",
      "credit_ltv": 100,
      "credit_ltv_setting": null
    }
  ]
}
```

`credit_ltv` is the asset's loan-to-value percentage for credit accounts — the haircut applied when it counts toward a credit line. `credit_ltv_setting` echoes the per-asset override when one is configured, `null` otherwise.

`issuer` is the owner account bound to an asset by operator-managed metadata. Assets without an issuer binding surface as the zero address. The issuer is a per-asset binding only and confers no privileges beyond the recorded mapping. The `assets` response does not include `cloid`; client operation ids are only observable via `txStatusByCloid` while a transaction is in the recent query window. `withdraw_fee_atoms` is canonical asset metadata in asset-local atoms; `withdraw_fee` is formatted from atoms using `balance_decimals`. On a [withdraw](/native-dev/build-with-native/native-core/reference/post-trade.md#withdraw) this fee is recorded (in the event and `/info withdraws`) but is **not** deducted from the balance.

### queryStatus

```json
{ "type": "queryStatus" }
```

Returns public query-view metadata and the retained recent-height window. This is the public way to discover the height bounds to pass to windowed reads such as [userFills](#userfills). Node role, writable/readable state, control-plane status, and other operational fields remain internal-only.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "oldest_available_height": 170001,
  "latest_available_height": 180000,
  "recent_query_window_blocks": 10000
}
```

All fields are `null` until the node has state to answer from.

### quoteAssets

```json
{ "type": "quoteAssets" }
```

Returns the current canonical quote-asset allowlist sorted by `asset_id`. `min_quantity` is the human-readable minimum order notional in the quote token; `min_quantity_atoms` is the raw integer atom quantity encoded as a decimal string for JavaScript safety.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "quote_assets": [
    {
      "asset_id": 1,
      "symbol": "USDC",
      "balance_decimals": 8,
      "min_quantity": "10",
      "min_quantity_atoms": "1000000000"
    }
  ]
}
```

### accountingWithdrawTokens

```json
{ "type": "accountingWithdrawTokens" }
```

Returns accounting withdraw-token rows sorted by `(chain_id, asset_id)`. `min_withdraw_atoms` is the canonical raw atom amount encoded as a decimal string; `min_withdraw_amt` is formatted using the asset's `balance_decimals`.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "accounting_withdraw_tokens": [
    {
      "chain_id": 1,
      "asset_id": 1,
      "symbol": "USDC",
      "balance_decimals": 8,
      "min_withdraw_amt": "1.25",
      "min_withdraw_atoms": "125000000"
    }
  ]
}
```

### accountingDepositContracts

```json
{ "type": "accountingDepositContracts" }
```

Returns the configured deposit source contracts sorted by `src_chain_id`. No parameters.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "accounting_deposit_contracts": [
    {
      "src_chain_id": 421614,
      "src_contract": "0xaabbccddeeff00112233445566778899aabbccdd"
    }
  ]
}
```

### multisigPolicy

Returns the dynamic non-admin multisig policy for one `scope`. Only the `ACCOUNTING` scope (`2`) is queryable; any other value returns HTTP 400 `InvalidMultisigScope`.

```json
{ "type": "multisigPolicy", "scope": "2" }
```

When a policy is set:

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "found": true,
  "policy": {
    "scope": 2,
    "scope_name": "ACCOUNTING",
    "threshold": null,
    "signers": []
  }
}
```

`threshold` and `signers` are always `null` and `[]` on this endpoint — the keys are present so the shape is stable, but the quorum itself is not published. Use `found` to learn whether a policy exists at all.

When none is set, `found` is `false` and the body echoes `scope` / `scope_name` instead of `policy`.

### markets

```json
{ "type": "markets" }
```

Response field `markets` is sorted by `market_id`. `price_decimals` and `max_price_sig_figs` are market metadata, as is `base_quantity_decimals`. `quote_balance_decimals` is derived from the quote asset and included as a convenience field for raw notional conversion. See [Decimals & Units](/native-dev/build-with-native/native-core/concepts/decimals-units.md).

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "markets": [
    {
      "market_id": 0,
      "base_asset_id": 3,
      "base_symbol": "ETH",
      "base_quantity_decimals": 4,
      "quote_asset_id": 1,
      "quote_symbol": "USDC",
      "quote_balance_decimals": 8,
      "price_decimals": 2,
      "max_price_sig_figs": 7,
      "credit_trading": true,
      "credit_trading_setting": null,
      "maker_fee_milli_bps": 2000,
      "taker_fee_milli_bps": 10000,
      "fee_setting": null
    }
  ]
}
```

`maker_fee_milli_bps` / `taker_fee_milli_bps` are the market's **effective** fee rates in milli-basis-points — never null, and the only programmatic source of what you will be charged. Divide by 1000 for basis points: `2000` is 2 bps maker, `10000` is 10 bps taker. `fee_setting` echoes the per-market override when one is configured, `null` otherwise.

`credit_trading` is the per-market gate for credit accounts. A credit account may trade a market when its own status is `active` **and** either `credit_trading` is true or the market appears in that account's `credit_trading_whitelisted_market_ids` (see [`spotCreditAccount`](#spotcreditaccount)). `credit_trading_setting` echoes the per-market override, `null` otherwise.

### l2Book

```json
{
  "type": "l2Book",
  "market_id": "0",
  "depth": "20"
}
```

`depth` is optional, defaults to `20`, and is capped by the server.

```json
{
  "found": true,
  "query_height": 180000,
  "app_hash": "0x...",
  "market_id": 0,
  "requested_depth": 20,
  "depth": 20,
  "max_depth": 100,
  "query_time_ms": 1785338171446,
  "last_update_height": 129475493,
  "last_update_ts_ms": 1785338171296,
  "bids": [
    { "price": "10", "quantity": "1.5", "order_count": 2 }
  ],
  "asks": []
}
```

`query_time_ms` is the block time of the view you read; `last_update_height` / `last_update_ts_ms` are when **this market's** book last changed. Together they are the staleness signal: a wide gap between `last_update_ts_ms` and `query_time_ms` means the book is quiet, not that your read is stale.

### withdraws

Per-user retained withdraw records (3-day window), sorted by `(block_height, tx_index, withdraw_nonce)`. Requires `user`. Each record adds `withdraw_fee_atoms` (recorded, not deducted).

```json
{ "type": "withdraws", "user": "0x0000000000000000000000000000000000000001" }
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "user": "0x0000000000000000000000000000000000000001",
  "withdraws": [
    {
      "tx_hash": "0x...",
      "block_height": 179999,
      "tx_index": 2,
      "block_timestamp_ms": 1717000000500,
      "asset_id": 1,
      "amount_atoms": "500000",
      "withdraw_fee_atoms": "1000",
      "dst_chain_id": 1,
      "dst_address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "withdraw_nonce": "7",
      "vault_address": "0xcccccccccccccccccccccccccccccccccccccccc"
    }
  ]
}
```

`vault_address` is the [vault contract](/native-dev/build-with-native/deposit-withdraw/vault-contract.md) the withdrawal pays out from. It is `null` on records written before vault payouts were activated.

### deposits

Per-user retained deposit records. Requires `user`; the response key is `user`.

**This is a view of the replay-protection window, not a deposit archive — an empty `deposits` array does not mean the account never deposited.** The window retains records by committed block timestamp for three days, and pruning is lazy: it is materialised when a new record is written into the same window. Deposit windows are keyed by `(src_chain_id, src_contract)` and therefore **shared across every account depositing through that contract**, so anyone's deposit prunes everyone's expired records — in practice the window holds close to exactly the last three days. Withdrawals prune the same way but their windows are keyed per account, so a low-frequency account's own withdrawals can stay visible far longer than three days; do not read that asymmetry as a guarantee.

For deposit or withdrawal history beyond that, join on `tx_hash` from the [explorer](https://app.native.org/explorer) rather than treating these endpoints as a ledger.

```json
{ "type": "deposits", "user": "0x0000000000000000000000000000000000000001" }
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "user": "0x0000000000000000000000000000000000000001",
  "deposits": [
    {
      "tx_hash": "0x...",
      "block_height": 179998,
      "tx_index": 0,
      "block_timestamp_ms": 1717000000200,
      "asset_id": 1,
      "amount_atoms": "1000000",
      "src_chain_id": 421614,
      "src_contract": "0xaabbccddeeff00112233445566778899aabbccdd",
      "deposit_nonce": "42"
    }
  ]
}
```

### userBalances

```json
{
  "type": "userBalances",
  "user": "0x0000000000000000000000000000000000000001"
}
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "balances": [
    {
      "asset_id": 0,
      "symbol": "USDC",
      "available": "1000",
      "locked": "0"
    }
  ]
}
```

### accountStatus

Whether an account exists and its freeze state.

```json
{
  "type": "accountStatus",
  "user": "0x0000000000000000000000000000000000000001"
}
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "found": true,
  "account_index": 5,
  "status": "active"
}
```

`found` is `true` once the account exists (it is created on its first deposit). `status` is `"active"` or `"frozen"`, and is `null` when `found` is `false`. `account_index` is the protocol's internal account index, `null` before the account exists.

### accountMultisig

The [account multisig](/native-dev/build-with-native/native-core/reference/account-multisig.md) quorum configured over an account's owner-signed actions, if any.

```json
{
  "type": "accountMultisig",
  "user": "0x0000000000000000000000000000000000000001"
}
```

```json
{
  "query_height": 198543355,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "found": true,
  "account_index": 93,
  "enabled": true,
  "role": null,
  "threshold": 2,
  "signers": ["0x...", "0x...", "0x..."],
  "policy_epoch": "7"
}
```

`found` reports whether the **account** exists, not whether it has a multisig row — read `enabled` to decide that. `role` is non-null only for protocol-operated accounts; for an ordinary account it is always `null`. `policy_epoch` is the value every quorum-signed request must carry, and it advances on each configuration change, so re-read it after any `setAccountMultisig`.

### spotCreditAccount

{% hint style="info" %}
`spotCreditAccount` and `spotCreditPositions` read a **credit account** — a protocol-granted account type distinct from the default spot (balance) account that [`userBalances`](#userbalances) reads. If you have not been granted a credit line, these report no position. See [Account Types](/native-dev/build-with-native/native-core/concepts/account-types.md).
{% endhint %}

```json
{
  "type": "spotCreditAccount",
  "user": "0x0000000000000000000000000000000000000001"
}
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "authorized": true,
  "status": "active",
  "credit_usd_atoms": 100000000000,
  "available_usd_atoms": "99850000000",
  "last_known_available_usd_atoms": "99850000000",
  "oracle_status": { "status": "available" },
  "credit_trading_whitelisted_market_ids": ["35"]
}
```

`credit_trading_whitelisted_market_ids` is always present. Its entries are market ids **as strings**, unlike every other market id in the API — compare them as strings.

For an owner with no credit line the response still returns every key, but `authorized` is `false` and `status`, `credit_usd_atoms`, `available_usd_atoms` and `last_known_available_usd_atoms` are all `null`. This is the common case, so type these four as nullable.

`status` is `"active"`, `"frozen"`, or `null`. `credit_usd_atoms` and the available fields are in `usd_atoms` (`USD_SCALE = 10^8`). [Credit & Margin](/native-dev/build-with-native/native-core/concepts/credit-margin.md) gives the formula behind `available_usd_atoms`, including the per-asset LTV and how a stale mark changes it. `available_usd_atoms` is `null` when the account cannot be valued: when a nonzero **short** position lacks a mark at the latest query height, or when the valuation overflows; a stale or missing mark on a long contributes `0` instead of nulling the figure. Accounts with no exposure can report their credit without marks. `last_known_available_usd_atoms` always uses the most recently committed marks regardless of staleness and is `null` only when a position asset has never had a mark. Negative fractional USD-atom position values are rounded down conservatively, matching the execution credit gate.

### spotCreditPositions

```json
{
  "type": "spotCreditPositions",
  "user": "0x0000000000000000000000000000000000000001"
}
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "spot_credit_positions": [
    {
      "asset_id": 2,
      "symbol": "ETH",
      "pending_exposure_qty": "-3",
      "actual_qty": "-2",
      "pending_exposure_display": "-0.0003",
      "actual_display": "-0.0002"
    }
  ]
}
```

A resting order commits a single leg: a resting ask only debits the base asset; a resting bid only debits the quote asset. The other leg appears as `actual_qty` only when a fill produces a real settlement delta. The amount committed differs by side — see [Credit & Margin](/native-dev/build-with-native/native-core/concepts/credit-margin.md#the-calculation).

### oracleStatus

```json
{ "type": "oracleStatus" }
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "oracle_status": { "status": "available" }
}
```

When the oracle is unavailable, `oracle_status` is `{ "status": "unavailable", "reason_code": "ProviderError" }` (or `MissingRoute`, `StalePrice`, `InvalidPrice`).

### markPrices

```json
{ "type": "markPrices", "asset_ids": [2, 4] }
```

`asset_ids` is optional; omitting it returns all known marks. The response is sorted by `asset_id`:

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "oracle_status": { "status": "available" },
  "mark_prices": [
    {
      "asset_id": 2,
      "usd_atoms": 300000000000,
      "updated_height": 180000,
      "source_ts_ms": 1760000000000
    },
    {
      "asset_id": 4,
      "usd_atoms": 5000000000000,
      "updated_height": 180000,
      "source_ts_ms": 1760000000050
    }
  ]
}
```

`usd_atoms` is the positive mark price scaled by `USD_SCALE = 10^8`. `updated_height` is the block in which that mark was last committed; a mark counts as **fresh** only when it equals the response's `query_height`, which is the comparison credit-account valuation uses — see [Credit & Margin](/native-dev/build-with-native/native-core/concepts/credit-margin.md#the-calculation). `source_ts_ms` is the upstream provider's per-feed timestamp (Binance spot: worker wall clock at response-fully-read; OKX: per-ticker `ts`; Binance futures: per-symbol `time` from `/fapi/v2/ticker/price`; Llama: per-coin `timestamp` (seconds) × 1000; cross routes use `min(base, quote)` of the inputs).

### openOrders

```json
{
  "type": "openOrders",
  "user": "0x0000000000000000000000000000000000000001",
  "market_id": "0"
}
```

Pass `market_id: -1` (as the number `-1` or the string `"-1"`) to get the owner's open orders across **every** market in one call. The response echoes `"market_id": -1` and applies the same 500-item cap and `truncated` flag over the union.

**Which 500 you get is not arbitrary, and it is not the newest.** Orders are ordered by `market_id` ascending, then by `oid` ascending within each market, and the cap takes the first 500 of that. So the union response fills up from the lowest market ids: an owner resting orders in many markets sees the low-numbered ones in full and the high-numbered ones **not at all** — not a thinned sample of each. There is no cursor or offset, so the remainder is unreachable through this call.

If `truncated` is `true`, query per market instead — pass a concrete `market_id` for each market you quote. A per-market count is normally far under the cap, and the union call is still the right way to discover *which* markets you hold orders in as long as it is not truncating.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "market_id": 0,
  "limit": 500,
  "truncated": false,
  "open_orders": [
    {
      "status": "open",
      "owner": "0x0000000000000000000000000000000000000001",
      "owner_index": 5,
      "market_id": 0,
      "oid": 773094113280001,
      "cloid": "0x11111111111111111111111111111111",
      "side": "bid",
      "price": "10",
      "original_qty": "1",
      "filled_qty": "0",
      "remaining_qty": "1",
      "created_tx_hash": "0x...",
      "created_height": 180000,
      "created_time_ms": 1760000000000
    }
  ]
}
```

`original_qty` is the submitted order quantity. For an order that partially fills and then rests on the book, `filled_qty` is nonzero and `remaining_qty` is the currently open quantity. `owner_index` is the protocol's internal account index for the owner; it also appears on the order objects returned by `orderStatus`.

This listing is deliberately lean. To read back how an order was placed — its `tif` and `order_type` — query [`orderStatus`](#orderstatus) for that single `oid`.

### userAgents

```json
{
  "type": "userAgents",
  "user": "0x0000000000000000000000000000000000000001"
}
```

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "agents": [
    { "slot_id": 0, "agent": "0x00000000000000000000000000000000000000aa", "epoch": 3 }
  ]
}
```

`agents` holds the active agent (API-wallet) slots for the owner. To sign `/trade` writes, read the `epoch` of the slot whose `agent` matches your API-wallet address and pass it as the envelope `agent_epoch`. `agent` is `null` for an empty slot.

### userFills

```json
{
  "type": "userFills",
  "user": "0x0000000000000000000000000000000000000001",
  "from_height": "179900",
  "to_height": "180000",
  "limit": "100"
}
```

`limit` must be positive and is capped at `max_limit` (500). Two different failure shapes, and the difference matters to your parser:

* A `limit` that is **missing, non-numeric, or beyond `u32`** is rejected with **HTTP 400** and `InvalidFillsQuery` — there is no `fills` key at all.
* A `limit` of `0`, a `from_height` above `to_height`, or a range wider than the recent window returns **HTTP 200** with an in-band `error` object (`InvalidFillsQuery`) and an empty `fills` array. A range that is short enough but starts *below* the retained window returns `HistoryWindowExceeded` instead, in the same shape — read `error.code`, not just the presence of `error`. On success the response echoes the effective `limit`, your `requested_limit`, and `max_limit`.

```json
{
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "from_height": 179900,
  "to_height": 180000,
  "limit": 100,
  "requested_limit": 100,
  "max_limit": 500,
  "fills": [
    {
      "height": 180000,
      "time": 1785338171296,
      "tid": 98956046499840256,
      "tx_index": 1,
      "tx_hash": "0x...",
      "market_id": 0,
      "role": "taker",
      "side": "B",
      "maker_oid": 773094113280001,
      "maker_cloid": "0x11111111111111111111111111111111",
      "maker_owner": "0x...",
      "taker_oid": 773094113280002,
      "taker_cloid": "0x22222222222222222222222222222222",
      "taker_owner": "0x...",
      "price": "10",
      "quantity": "1",
      "fee_asset_id": 1,
      "fee": "0.0001",
      "fee_mode": "credit"
    }
  ]
}
```

`time` is the fill's block timestamp in milliseconds. `side` is the **querying owner's own** direction (`B` bought, `A` sold), and is `null` once the taker order has aged out of the window.

`tid` is the unique id of the trade. The WebSocket [`trades`](/native-dev/build-with-native/native-core/reference/websocket.md#trades) and [`userFills`](/native-dev/build-with-native/native-core/reference/websocket.md#userfills) frames report the same value, so a backfill merges into a live stream on it. Parse it as a `BigInt`, not a JS `Number`.

Each fill reports only the **querying owner's side** fee (chosen by `role`). `fee_asset_id` is the asset the fee was charged in (the asset that side received: base for a bid, quote for an ask), `fee` is the decimal amount formatted with that asset's `balance_decimals`, and `fee_mode` is `"balance"` or `"credit"`. (Internally — in canonical events and the query model — the fee `amount` is a raw atom; only this public JSON `fee` is the formatted decimal string.) Trading fees activate at a network-specific height defined by the code protocol-rules schedule. There are two distinct null cases:

* **Pre-activation / no-fee fill** (below the network's fee activation height): `"fee_asset_id": null, "fee": "0", "fee_mode": null`.
* **Fee-active fill whose fee floored to zero**: non-null `fee_asset_id` and `fee_mode` with `"fee": "0"` — so clients can tell the fee regime is live.

The response shape is unchanged, but the `fee` amount may reflect code-defined per-market and per-account fee overrides (the effective rate is the minimum of the global rate and any matching market/account override). A fully waived market appears as a fee-active fill with `"fee": "0"` (the second null case above).

### userFillsByTime

Per-user fills over a wall-clock window, sorted oldest first. Requires `user` and `start_time_ms`; `end_time_ms` is inclusive like `start_time_ms` and defaults to the newest indexed block. At most 1000 fills per response, and only the 10000 most recent fills are available.

```json
{
  "type": "userFillsByTime",
  "user": "0x0000000000000000000000000000000000000001",
  "start_time_ms": 1785338100000,
  "end_time_ms": 1785338171296
}
```

```json
{
  "fills": [
    {
      "height": 180000,
      "time": 1785338171296,
      "tid": 98956046499840256,
      "tx_index": 1,
      "tx_hash": "0x...",
      "market_id": 0,
      "role": "taker",
      "side": "B",
      "maker_oid": 773094113280001,
      "maker_cloid": "0x11111111111111111111111111111111",
      "maker_owner": "0x...",
      "taker_oid": 773094113280002,
      "taker_cloid": "0x22222222222222222222222222222222",
      "taker_owner": "0x...",
      "price": "10",
      "quantity": "1",
      "fee_asset_id": 1,
      "fee": "0.0001",
      "fee_mode": "credit"
    }
  ]
}
```

To page, pass the last fill's `time` as the next `start_time_ms`. The bound is inclusive, so that fill repeats — deduplicate on `tid`.

### orderStatus

Query by server order id:

```json
{
  "type": "orderStatus",
  "oid": "773094113280001"
}
```

Query by client order id:

```json
{
  "type": "orderStatus",
  "user": "0x0000000000000000000000000000000000000001",
  "market_id": "0",
  "cloid": "0x11111111111111111111111111111111"
}
```

`orderStatus` checks the current open-order view, then falls back to the latest retained status record for that `oid` or `(user, market_id, cloid)`. Status records are action-result records, not a complete lifecycle stream: a successful incoming order writes one record even when it rests on the book, but later passive maker fills are exposed through `userFills` and open-order state changes rather than a new status record for the maker order. Explicit cancel and modify actions do write status records for the affected resting order. Outcome-stage failures inside a **`batch`** also write retained status records when execution has enough order context: `ioccancel`, `fokcancel`, `badalopx`, `marketordernoliquidity`, and `insufficientspotcredit` can be queried by `oid` or `(user, market_id, cloid)` inside the recent query window. A **single** `order` action that fails this way writes no such record — the whole transaction fails, and the outcome is only in the synchronous [`POST /trade`](/native-dev/build-with-native/native-core/reference/post-trade.md) response.

Order-validation failures write no status record at all. Tick size, minimum notional, and insufficient balance are checked *before* the order is assigned an order id, so there is nothing to look up: they are reported only on the synchronous [`POST /trade`](/native-dev/build-with-native/native-core/reference/post-trade.md) response. This is the same `found: false` you get for a tx whose block is not published yet, so `orderStatus` alone cannot tell a refused order from one still in flight — read the `/trade` response.

A partially filled order that is still on the book reports `status: "partiallyfilledresting"`, not `"open"` — match on both if you are testing for "still working".

For an in-flight order — a `/trade` you just sent — you do **not** poll `orderStatus` to learn the outcome: the synchronous `/trade` response already carries it in its [`response` envelope](/native-dev/build-with-native/native-core/reference/post-trade.md#what-accepted-carries). A just-submitted transaction reads back `found: false` here until its block is published.

Open order response:

```json
{
  "found": true,
  "query_height": 180000,
  "app_hash": "0x...",
  "status": "open",
  "order": {
    "status": "open",
    "market_id": 0,
    "oid": 773094113280001,
    "cloid": "0x11111111111111111111111111111111",
    "tif": "alo",
    "order_type": "limit",
    "remaining_qty": "1"
  }
}
```

Terminal order response:

```json
{
  "found": true,
  "query_height": 180000,
  "app_hash": "0x...",
  "order": {
    "status": "filled",
    "market_id": 0,
    "oid": 773094113280001,
    "cloid": "0x11111111111111111111111111111111",
    "tif": "gtc",
    "order_type": "limit",
    "filled_qty": "1",
    "remaining_qty": "0",
    "is_resting": false,
    "updated_tx_hash": "0x...",
    "updated_height": 180000
  }
}
```

`tif` and `order_type` echo the [order action](/native-dev/build-with-native/native-core/reference/post-trade.md#order) the order was submitted with, in the same spelling you sent: `tif` is `"gtc"`, `"ioc"`, `"fok"`, or `"alo"`, and `order_type` is `"limit"` or `"market"`. They are most useful on the failure statuses — `ioccancel`, `fokcancel`, and `badalopx` are each explained by the `tif` the order carried:

```json
{
  "status": "ioccancel",
  "oid": 773094113280002,
  "tif": "ioc",
  "order_type": "market",
  "filled_qty": "0",
  "remaining_qty": "1",
  "is_resting": false
}
```

The [`openOrders`](#openorders) listing does not carry either field — it is the bulk view, and a caller listing hundreds of its own resting orders already knows how it placed them.

Not found:

```json
{
  "found": false,
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x0000000000000000000000000000000000000001",
  "market_id": 0,
  "cloid": "0x11111111111111111111111111111111"
}
```

A bare `found: false` means there is no such order inside the retained window. When you look up by `oid` and that `oid` was created *before* the window starts, the response says so explicitly instead — still **HTTP 200**, with an in-band `error` beside the usual fields:

```json
{
  "found": false,
  "query_height": 199793262,
  "app_hash": "0x...",
  "oid": 3181166949566721,
  "error": {
    "code": "HistoryWindowExceeded",
    "requested": 189612326,
    "oldest_available_height": 199783263,
    "latest_available_height": 199793262,
    "recent_query_window_blocks": 10000,
    "message": "This node keeps detailed query data only for the most recent configured block window. The requested data is older than the local query window."
  }
}
```

Treat that as "too old to answer", not as "no such order"; `oldest_available_height` tells you where the window currently starts. The `user` + `market_id` + `cloid` form carries no height, so it cannot draw that distinction — an aged-out order comes back there as a plain `found: false`. For anything past the window, keep your own record of the order from when you placed it.

A query that carries neither a parseable `oid` nor a complete `user` + `market_id` + `cloid` triple is rejected with **HTTP 400** and `InvalidOrderStatusQuery`. A malformed `market_id` or `cloid` is rejected the same way, as `InvalidMarketId` / `InvalidCloid`.

### txStatusByCloid

Transaction status by `cloid` uses the transaction authority as the `user` namespace and does not require a market id. For `transfer`, `activateFor`, `withdraw`, `settle`, `repay`, and `setAccountMultisig`, `user` is the recovered signer authority — or, when the action was submitted under an [account multisig](/native-dev/build-with-native/native-core/reference/account-multisig.md), the `auth_account`.

```json
{
  "type": "txStatusByCloid",
  "user": "0x00ee41b8f0dd58806f14b30fb11994673769b25c",
  "cloid": "0x11111111111111111111111111111111"
}
```

```json
{
  "found": true,
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x00ee41b8f0dd58806f14b30fb11994673769b25c",
  "cloid": "0x11111111111111111111111111111111",
  "tx_hash": "0x...",
  "tx_index": 1,
  "status": "success",
  "action_type": "settle"
}
```

Failed retained tx responses use the lower-case committed execution error code as `status`, matching the `orderStatus` style where terminal failures are reported through the status string:

```json
{
  "found": true,
  "query_height": 180000,
  "app_hash": "0x...",
  "owner": "0x00ee41b8f0dd58806f14b30fb11994673769b25c",
  "cloid": "0x11111111111111111111111111111111",
  "tx_hash": "0x...",
  "tx_index": 1,
  "status": "invalidsettle",
  "action_type": "settle"
}
```

If a failed retained tx has no single committed error code in its payload, `status` remains `"failed"`.

The public `settle`/`repay` actions also surface here, with `action_type` `"settle"` / `"repay"`. Their `user` namespace is the **recovered signer** (settle → margin owner; repay → cash owner) — or the `auth_account` under an [account multisig](/native-dev/build-with-native/native-core/reference/account-multisig.md) — so a counterparty named in the action body cannot find the tx. This window is the only `cloid`-keyed lookup for settle/repay: there is no idempotency, only recent-window visibility bounded by `max_recent_txs`. The same `cloid` resubmitted under a new envelope `nonce` is a separate tx; a lookup returns the latest retained one, and once a tx ages out of the window the response is `found: false`. A failed settle/repay is retained the same way and reports `status` `"invalidsettle"` / `"invalidrepay"` (or another lower-case committed error code).
