# Nexus Trades API

> Open, trim, close and read your Nexus paper trades from your own bot or script, with your Skylit API key.

> **Beta.** Your Skylit API key works here. Create one on the [Developer page](https://app.skylit.ai/developer).

The **Nexus Trades API** lets your own bot open, trim, close and read your Nexus **paper** trades. Options and stock orders go to your Nexus paper wallet. Futures orders go to your practice account, or to an evaluation or funded account you name. Every order goes through the same checks, the same live prices and the same account rules as an order you place by hand in Nexus.

What it isn't:

- **It never sends trades to a broker.** Nothing here reaches a real market, even if you've connected a broker to Nexus. The idea is that your bot already traded wherever it trades, and it mirrors that trade into Nexus.
- **It isn't a way to pick your own fill.** The server prices every fill from the live market when your request arrives. You can't send a time.
- **It isn't a market data feed.** It tells you what you can trade and what you hold. It doesn't stream quotes, and it doesn't serve futures market data (the [market data API](https://www.skylit.ai/docs/api-reference/introduction) and the [MCP server](https://www.skylit.ai/docs/mcp/overview) don't either).

> **Info:** **Two different APIs, one key.** The [market data API](https://www.skylit.ai/docs/api-reference/introduction) on `api.skylit.ai` is read-only: it never places an order. The Nexus Trades API on `app.skylit.ai` is the only place your key can write, and all it writes is paper trades in your own Nexus accounts.

All tickers, prices, ids and times on this page are made up to show the shape of a call. They aren't real fills and they aren't results.

## What you need

- A **Skylit API key**. Make one on the [Developer page](https://app.skylit.ai/developer), which also has this API's reference under **API reference > [Nexus Trades](https://app.skylit.ai/developer/api?product=nexus-trades)**.
- A Skylit account that **includes Nexus**.
- For futures, **futures paper trading** on your account. Without it, futures calls answer `403 futures_access_denied`.

Trades API calls don't spend API credits. They have their own [rate limits](#rate-limits).

## Base URL

```bash
https://app.skylit.ai
```

Every route lives under `/api/nexus/v1/`.

| Method | Path | What it does |
|---|---|---|
| GET | `/api/nexus/v1/trading/capabilities` | What this key can trade right now |
| POST | `/api/nexus/v1/trades` | Open a trade (options, stocks or futures) |
| POST | `/api/nexus/v1/trades/{id}/exits` | Trim or close an options or stock trade |
| GET | `/api/nexus/v1/trades` | List your options and stock trades |
| GET | `/api/nexus/v1/trades/{id}` | Read one trade |
| GET | `/api/nexus/v1/trading/accounts/{account}` | A futures account: balance, day P/L, rules, positions |
| GET | `/api/nexus/v1/trading/accounts/{account}/orders` | The futures orders your keys placed in that account |
| POST | `/api/nexus/v1/trading/accounts/{account}/close` | Close one futures position, or flatten the account |
| GET | `/api/nexus/v1/trading/accounts/{account}/orders/working` | The futures orders resting on that account |
| GET | `/api/nexus/v1/trading/accounts/{account}/orders/{orderId}` | One futures order as it stands now |
| POST | `/api/nexus/v1/trading/accounts/{account}/orders/{orderId}/cancel` | Cancel one resting futures order |
| POST | `/api/nexus/v1/trading/accounts/{account}/orders/{orderId}/modify` | Move a resting futures order's price |
| POST | `/api/nexus/v1/trading/accounts/{account}/orders/cancel` | Cancel every resting order on the account, or on one contract |

Each one has its own page, with every field and an example, in the [API Reference](https://www.skylit.ai/docs/api-reference/account-and-capabilities/what-this-key-can-trade-right-now) tab of the Nexus section. The same reference is in the app, under **Developer > API reference > [Nexus Trades](https://app.skylit.ai/developer/api?product=nexus-trades)**. The OpenAPI spec is at `https://www.skylit.ai/docs/nexus-trades-openapi.yaml`.

## Quick start

### 1. Keep your key out of your code

```bash
export SKYLIT_API_KEY="your key here"
export NEXUS="https://app.skylit.ai"
```

### 2. Ask what you can trade

```bash
curl -s "$NEXUS/api/nexus/v1/trading/capabilities" \
  -H "Authorization: Bearer $SKYLIT_API_KEY"
```

```json
{
  "data": {
    "assetClasses": [
      { "assetClass": "options", "available": true, "orderTypes": ["market"] },
      { "assetClass": "stocks", "available": true, "orderTypes": ["market"] },
      { "assetClass": "futures", "available": true, "orderTypes": ["market", "limit", "stop"] }
    ],
    "orderTypes": ["market"],
    "testOrders": true,
    "futures": {
      "marketOpen": true,
      "maxOrderQuantity": 100,
      "orderTypes": ["market", "limit", "stop"],
      "bracket": ["stopLoss", "takeProfit", "trailingStop"],
      "modify": ["price"],
      "contracts": [
        {
          "root": "NQ",
          "name": "E-mini Nasdaq-100",
          "symbol": "NQZ26",
          "tickSize": 0.25,
          "tickValueCents": 500,
          "pointValueCents": 2000,
          "commissionPerSideCents": 214,
          "livePrice": true
        }
      ],
      "accounts": [
        {
          "id": "3c2b1a90-5e4d-4f3a-8b2c-1d0e9f8a7b6c",
          "kind": "practice",
          "name": "Paper",
          "status": "active",
          "balanceCents": 5000000,
          "tradable": true,
          "allowed": ["read", "open", "reduce", "close", "limit", "stop", "bracket", "cancel", "modify"]
        }
      ]
    },
    "limits": { "writesPerMinute": 30, "readsPerMinute": 120 }
  }
}
```

Call this first. Your bot never has to guess which asset classes, contracts and accounts it can trade. When the futures market is closed, `futures.reopensAt` says when it opens again.

### 3. Rehearse with a test order

Add `"test": true`. The order is checked and priced like the real one, then it isn't placed.

```bash
curl -s -X POST "$NEXUS/api/nexus/v1/trades" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ticker":"SPY","direction":"call","strike":600,"expiration":"2026-10-16","quantity":2,"test":true}'
```

See [Test orders](https://www.skylit.ai/docs/nexus-trades/test-orders) for what a test checks and what it can't.

### 4. Open a real paper trade

```bash
curl -s -X POST "$NEXUS/api/nexus/v1/trades" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ticker": "SPY",
    "direction": "call",
    "strike": 600,
    "expiration": "2026-10-16",
    "quantity": 2,
    "clientOrderId": "spy-open-0001"
  }'
```

It answers `201` with the trade in `data` and the fill in `meta.fill`. Keep `data.id`: you need it to trim or close the trade.

### 5. Trim it, then close it

```bash
# Trim 1 of 2
curl -s -X POST "$NEXUS/api/nexus/v1/trades/TRADE_ID/exits" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"quantity":1,"clientOrderId":"spy-trim-0001"}'

# Close the rest
curl -s -X POST "$NEXUS/api/nexus/v1/trades/TRADE_ID/exits" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"closeAll":true,"clientOrderId":"spy-close-0001"}'
```

### Futures in one call

```bash
curl -s -X POST "$NEXUS/api/nexus/v1/trades" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assetClass":"futures","ticker":"NQ","side":"buy","quantity":1,"clientOrderId":"nq-open-0001"}'
```

No `account` in the body means your practice account. An order never lands in an evaluation or a funded account unless you name it (`"account": "evaluation"`, `"funded"` or the account's id).

Futures net per account and contract, so there's no trade id to exit. Close with `POST /api/nexus/v1/trading/accounts/{account}/close`: send a `ticker` to close one position, or `"closeAll": true` to flatten. It closes exactly what you hold and can't flip you.

## Your key

Send your Skylit API key on every call:

```text
Authorization: Bearer YOUR_SKYLIT_API_KEY
```

An `X-API-Key: YOUR_SKYLIT_API_KEY` header works too. If you send both, `Authorization` wins.

- A key only ever sees and trades its owner's trades and accounts. An id that belongs to someone else answers `404`, the same as an id that doesn't exist.
- A key you revoke stops working here within about a minute.
- Treat it like a password: keep it in an environment variable, never in source control or a shared prompt.

## Requests and responses

- Send JSON, one object per request, 8 KB at most.
- **Unknown fields are refused.** A typo like `"quantitty"` gets `400 bad_request` naming the field. It's never silently ignored.
- Success answers `{"data": ..., "meta": ...}`. `meta` is left out when there's nothing to add.
- A failure answers `{"error": {"code": "...", "message": "..."}}`. Branch on `code`. The `message` is written for a person and can change.
- Responses are never cached (`Cache-Control: no-store`).

## Rate limits

Every request counts, refused ones included, so hammering a refused call keeps you throttled.

| Limit | Applies to | Amount |
|---|---|---|
| Writes per key | POSTs from one key | 30 per minute |
| Reads per key | GETs from one key | 120 per minute |
| Writes per member | POSTs across all your keys | 60 per minute |
| Reads per member | GETs across all your keys | 240 per minute |
| Burst | POSTs across all your keys | 5 in any 5 seconds |
| Per network client | Every request, before the key is checked | 300 per minute |

Each answer carries `X-RateLimit-Limit` and `X-RateLimit-Remaining` for your per-key limit (writes on a POST, reads on a GET). Over a limit you get `429 rate_limited` with a `Retry-After` header in seconds. Wait that long, then retry. Your key's general Skylit API rate limit applies too, with the same `429`.

You can hold at most **50 open options and stock positions opened through the API**. The 51st open answers `409 open_position_limit`. Futures positions don't count toward it.

## Retries and clientOrderId

Networks drop answers. If your bot sends an order and never hears back, it can't tell whether the order filled. Send a `clientOrderId` you make up, and retry with the same one: you get the first order back and nothing new is placed.

- **Format:** 1 to 64 characters. Letters, digits, and `_` `.` `:` `-`.
- **Where:** the `clientOrderId` field in the body, or an `Idempotency-Key` header. If you send both they must match.
- **How long:** 24 hours, per member, across all your keys, in one id space for options, stocks and futures.
- **A repeat** answers `200` with the original order and `meta.idempotentReplay: true`. (The first answer to an open is `201`.)
- **A repeat while the first copy is still running** answers `409 request_in_progress` with `Retry-After: 1`. Wait and send it again.
- **The same id on a different kind of request** answers `409 client_order_id_used`. An id that opened a trade can't close one.
- **Test orders have their own id space,** so a bot can go from test mode to live without changing how it makes ids.
- **A futures close that finds nothing to close** doesn't use up its id.

> **Tip:** Got `500 internal_error` on an order you sent with a `clientOrderId`? Retry with the same id. It can't fill twice. On futures there's no duplicate-position check, so a retry without an id adds to your position. Always send one.

## Time rules

You never send a time. The fill time is the server's clock when your request arrives.

**Options and stocks** only fill while their market is open (all times ET):

| What | Fills from | Until |
|---|---|---|
| Stocks | 09:30 | 16:00 |
| Most options | 09:30 | 16:00 |
| SPY, QQQ, IWM options that aren't expiring today | 09:30 | 16:15 |
| SPX, SPXW, XSP, RUT, RUTW, VIX, VIXW options that aren't expiring today | 09:30 | 17:00 |
| Any option on its last trading day | 09:30 | 16:00 |
| Early-close days | 09:30 | 13:00 (13:15 for SPY, QQQ, IWM options that aren't expiring) |

Weekends and US market holidays are closed.

**Futures** trade Sunday 18:00 ET to Friday 17:00 ET, with a break from 17:00 to 18:00 ET each day. Exchange holidays aren't modelled: on one, the session reads as open, there's no live price, and orders answer `503 no_live_price`.

Outside those hours an order answers `422 market_closed` and nothing fills. It isn't queued for the open.

## Price rules

**The server prices the fill,** for every asset class.

| Asset class | A buy or an open fills at | A sell or a close fills at |
|---|---|---|
| Options | the live ask | the live bid |
| Stocks | the live price | the live price |
| Futures | the last trade, one tick worse for you | the last trade, one tick worse for you |

A fill needs a live price. When the quote is too old you get `503 stale_quote`, and with no live price `503 no_live_price`. A futures market order that opens or adds needs a trade printed in the last 15 seconds. A close still works on a quiet price. All of these are safe to retry after a moment.

**Your own price (options and stocks only).** If your bot filled somewhere else and you want Nexus to record that price, send `price`. It's only taken inside a small window around the live quote:

```text
bid - tolerance  <=  price  <=  ask + tolerance
```

| Asset class | Tolerance |
|---|---|
| Options | the larger of 2 ticks or 1% of the mid. A tick is 0.01 under 3.00 and 0.05 from 3.00 up |
| Stocks | the larger of 0.02 or 0.1% of the live price |

Outside the window, `ifOutside` decides:

| `ifOutside` | Result |
|---|---|
| `"reject"` (the default) | `422 price_outside_market`. The message shows the bid and ask it was checked against. Nothing fills |
| `"market"` | The order fills at the server's market price instead |

`meta.fill.priceSource` says which price was used: `"client"` (yours) or `"market"` (the server's).

**Futures take no fill price.** A market order fills at the price the server sees. A limit or stop order carries the price it rests at, and fills when the market trades through it, the same as an order from the Nexus futures ticket. An API order can never fill better than the same order placed by hand.

## A worked example

A small loop on the futures practice account: check what's tradable, rehearse with a test order, place the real one with a `clientOrderId`, read the position, flatten. Both versions read the key from the environment.

```python Python
import os
import time
import uuid

import requests

BASE = "https://app.skylit.ai"
KEY = os.environ["SKYLIT_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

RETRY_STATUSES = {429, 503}

class ApiError(Exception):
    def __init__(self, status, code, message):
        super().__init__(f"{status} {code}: {message}")
        self.status, self.code = status, code

def call(method, path, body=None, tries=4):
    """One API call. Waits and retries on 429 and 503, raises on anything else."""
    for attempt in range(tries):
        resp = requests.request(method, BASE + path, headers=HEADERS, json=body, timeout=10)
        if resp.ok:
            return resp.json()
        err = resp.json().get("error", {})
        if resp.status_code in RETRY_STATUSES and attempt < tries - 1:
            time.sleep(int(resp.headers.get("Retry-After", "2")))
            continue
        raise ApiError(resp.status_code, err.get("code"), err.get("message"))

def main():
    # 1. What can this key trade right now?
    caps = call("GET", "/api/nexus/v1/trading/capabilities")["data"]
    futures = caps.get("futures")
    if not futures or not futures["marketOpen"]:
        print("Futures aren't tradable right now.")
        return
    nq = next((c for c in futures["contracts"] if c["root"] == "NQ"), None)
    if not nq or not nq["livePrice"]:
        print("No live NQ price right now.")
        return

    order = {"assetClass": "futures", "ticker": "NQ", "side": "buy", "quantity": 1}

    # 2. Rehearse. Same checks and price as the real order, nothing placed.
    if caps["testOrders"]:
        test = call("POST", "/api/nexus/v1/trades", {**order, "test": True})
        print("Test would fill at", test["data"]["fillPrice"])

    # 3. The real order. The clientOrderId makes a retry safe.
    coid = "nq-" + uuid.uuid4().hex[:16]
    try:
        placed = call("POST", "/api/nexus/v1/trades", {**order, "clientOrderId": coid})
    except requests.RequestException:
        # No answer. Send the SAME id again: it can't fill twice.
        placed = call("POST", "/api/nexus/v1/trades", {**order, "clientOrderId": coid})
    print("Filled", placed["data"]["symbol"], "at", placed["data"]["fillPrice"])

    # 4. Read the position.
    state = call("GET", "/api/nexus/v1/trading/accounts/practice")["data"]
    for pos in state["positions"]:
        pnl = pos.get("openPnlCents")  # left out when there's no price
        shown = "n/a" if pnl is None else f"${pnl / 100:.2f}"
        print(pos["side"], pos["quantity"], pos["symbol"], "open P/L", shown)

    # 5. Flatten. Safe to send twice: it only closes what's open.
    closed = call("POST", "/api/nexus/v1/trading/accounts/practice/close", {"closeAll": True})
    for o in closed["data"]["closed"]:
        print("Closed", o["quantity"], o["symbol"], "at", o["fillPrice"])

if __name__ == "__main__":
    try:
        main()
    except ApiError as e:
        print("Stopped:", e)
```

```javascript JavaScript
// Node 18 or newer. Save as a .mjs file so `import` works.
import { randomUUID } from "node:crypto";

const BASE = "https://app.skylit.ai";
const KEY = process.env.SKYLIT_API_KEY;

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// One API call. Waits and retries on 429 and 503, throws on anything else.
async function call(method, path, body, tries = 4) {
  for (let attempt = 0; attempt < tries; attempt++) {
    const resp = await fetch(BASE + path, {
      method,
      headers: {
        Authorization: `Bearer ${KEY}`,
        ...(body ? { "Content-Type": "application/json" } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const json = await resp.json();
    if (resp.ok) return json;
    if ((resp.status === 429 || resp.status === 503) && attempt < tries - 1) {
      await sleep(Number(resp.headers.get("Retry-After") ?? 2) * 1000);
      continue;
    }
    const err = json.error ?? {};
    throw Object.assign(new Error(`${resp.status} ${err.code}: ${err.message}`), {
      status: resp.status,
      code: err.code,
    });
  }
}

async function main() {
  // 1. What can this key trade right now?
  const caps = (await call("GET", "/api/nexus/v1/trading/capabilities")).data;
  const futures = caps.futures;
  if (!futures || !futures.marketOpen) {
    console.log("Futures aren't tradable right now.");
    return;
  }
  const nq = futures.contracts.find((c) => c.root === "NQ");
  if (!nq || !nq.livePrice) {
    console.log("No live NQ price right now.");
    return;
  }

  const order = { assetClass: "futures", ticker: "NQ", side: "buy", quantity: 1 };

  // 2. Rehearse. Same checks and price as the real order, nothing placed.
  if (caps.testOrders) {
    const test = await call("POST", "/api/nexus/v1/trades", { ...order, test: true });
    console.log("Test would fill at", test.data.fillPrice);
  }

  // 3. The real order. The clientOrderId makes a retry safe.
  const clientOrderId = "nq-" + randomUUID().replaceAll("-", "").slice(0, 16);
  let placed;
  try {
    placed = await call("POST", "/api/nexus/v1/trades", { ...order, clientOrderId });
  } catch (e) {
    if (e.status) throw e; // the API answered: a real refusal
    // No answer. Send the SAME id again: it can't fill twice.
    placed = await call("POST", "/api/nexus/v1/trades", { ...order, clientOrderId });
  }
  console.log("Filled", placed.data.symbol, "at", placed.data.fillPrice);

  // 4. Read the position.
  const state = (await call("GET", "/api/nexus/v1/trading/accounts/practice")).data;
  for (const pos of state.positions) {
    // openPnlCents is left out when there's no price
    const shown = pos.openPnlCents == null ? "n/a" : `$${(pos.openPnlCents / 100).toFixed(2)}`;
    console.log(pos.side, pos.quantity, pos.symbol, "open P/L", shown);
  }

  // 5. Flatten. Safe to send twice: it only closes what's open.
  const closed = await call("POST", "/api/nexus/v1/trading/accounts/practice/close", { closeAll: true });
  for (const o of closed.data.closed) {
    console.log("Closed", o.quantity, o.symbol, "at", o.fillPrice);
  }
}

main().catch((e) => console.log("Stopped:", e.message));
```

## Error codes

Every error has the same shape:

```json
{ "error": { "code": "duplicate_position", "message": "You already hold an open position on this contract. Close it first, or add to it in Nexus.", "tradeId": "6e0d7a2b-3c1f-4b7e-a1d5-9f2c8e4b6a01" } }
```

`tradeId` only comes with `duplicate_position`.

> **Note:** **Rule of thumb for bots.** A 4xx means the request won't work as sent, so don't loop on it. 429 and 503 mean wait and retry (every `503` carries a `Retry-After` header). 500 means retry with the same `clientOrderId`.

### Key and access

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 401 | `missing_api_key` | No key on the request | Send `Authorization: Bearer YOUR_SKYLIT_API_KEY` |
| 401 | `invalid_api_key` | The key is wrong or was revoked | Stop. Create a new key |
| 403 | `account_suspended` | API access is suspended for this account | Stop. Contact support |
| 403 | `nexus_access_denied` | This Skylit account doesn't include Nexus | Stop. Nothing to retry |
| 403 | `futures_access_denied` | This account doesn't have futures paper trading yet | Stop sending futures calls |
| 429 | `rate_limited` | Too many requests | Wait `Retry-After` seconds, then retry |
| 503 | `trades_api_disabled` | The trades API is switched off right now | Wait and retry later |
| 503 | `api_keys_unavailable` | API keys can't be used here right now | Wait and retry later |
| 503 | `auth_unavailable` | Your key couldn't be checked right now | Retry in a moment |
| 503 | `access_check_unavailable` | Nexus access couldn't be checked right now | Retry in a moment |

### The request itself

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 400 | `bad_request` | A field is wrong, unknown, or doesn't belong on this kind of order. The message says which | Fix the request |
| 400 | `bad_contract` | The ticker, strike, direction, expiration or `contract` text couldn't be read | Fix the contract |
| 400 | `invalid_quantity` | `quantity` is missing or out of range, or you sent `quantity` and `closeAll` together | Fix the size |
| 400 | `invalid_price` | `price` is out of range, zero or missing on a move | Fix the price, or leave it out |
| 400 | `only_market_orders` | An options or stock `orderType` other than `market` | Leave `orderType` out |
| 400 | `unsupported_asset_class` | That asset class isn't tradable here | Check `capabilities` |
| 400 | `test_orders_unavailable` | Test orders aren't on right now | Leave `test` out, or wait |

### Options and stocks

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 404 | `trade_not_found` | No trade with that id on your account | Check the id. On a `"test": true` exit: that id isn't a test order, and nothing was closed |
| 409 | `duplicate_position` | You already hold this contract. `tradeId` says which trade | Exit that trade first, or skip |
| 409 | `trade_closed` | The trade is already closed | Stop trying to exit it |
| 409 | `open_position_limit` | You have 50 open positions opened through the API | Close some first |
| 422 | `market_closed` | The market is closed for this contract | Wait for the open. Nothing was queued |
| 422 | `contract_expired` | The contract has expired or stopped trading for the day | Pick a live contract |
| 422 | `insufficient_buying_power` | Not enough paper buying power | Send a smaller size |
| 422 | `price_outside_market` | Your `price` is outside the window. The message shows the bid and ask | Resend with a closer price, with `ifOutside: "market"`, or with no price |
| 503 | `no_live_price` | No live price for this contract right now | Retry in a moment |
| 503 | `stale_quote` | The last quote is too old to fill against | Retry in a moment |

### Retries

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 409 | `client_order_id_used` | This id already did something different in the last 24 hours | Use a new id for a new order |
| 409 | `request_in_progress` | An order with this id is still being processed | Wait `Retry-After` (1 second) and send it again |

### Futures

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 404 | `account_not_found` | No such futures account on your Skylit account, or no running evaluation or funded account | Read `capabilities` for your account ids |
| 409 | `account_locked` | The account isn't taking orders. Usually the daily loss limit, and the message says when it reopens | Stop opening. You can still close |
| 409 | `account_ended` | The account has failed, passed, been closed or archived | Stop trading this account |
| 409 | `account_busy` | The account is busy with another order | Wait `Retry-After` (1 second), then retry |
| 422 | `account_ambiguous` | `"evaluation"` or `"funded"` with more than one running. The message lists the ids | Send the id you mean |
| 422 | `account_not_tradable` | A challenge entry, or a kind of account the API can't trade | Don't trade it through the API. You can read it |
| 422 | `contract_not_offered` | That product isn't offered | Read `capabilities` for the list |
| 422 | `contract_expired` | That contract month has expired | Send the product (like `NQ`) to get the front month |
| 422 | `market_closed` | The futures market is closed. The message says when it reopens | Wait. Check `futures.reopensAt` |
| 422 | `reduce_only_would_open` | The `reduceOnly` order would open, add to or flip a position | Re-read the account. You're probably already flat |
| 422 | `order_refused` | A rule said no: the contract cap, your risk settings, the flat-by-close window, a rolled contract, or a resting order's own checks. The message says which | Read the message. Don't resend unchanged |
| 503 | `no_live_price` | No fresh price for this contract | Retry in a moment. A close still works when the market has only gone quiet |
| 503 | `rules_pending` | Your risk rules couldn't be checked just now, so the order wasn't placed | Wait `Retry-After` (1 second), then retry |

### Futures resting orders

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 400 | `unsupported_order_type` | A futures `orderType` that isn't `market`, `limit` or `stop` | Use one of the three |
| 400 | `test_modify_unavailable` | You tried to move a test order | Cancel it and send a new test order |
| 404 | `order_not_found` | No order with that id in that account | Check the account in the path. Read `orders/working` |
| 409 | `order_not_working` | The order has already filled, was cancelled or was rejected. Nothing was changed | Read the order. If it filled, you have a position |

### Our side

| Status | `code` | What it means | What your bot should do |
|---|---|---|---|
| 500 | `internal_error` | Something went wrong on our side | With a `clientOrderId`: retry with the same id. Without one: read your trades or account before you retry |
| 503 | `unavailable` | Something we needed couldn't be read right now. Nothing was sent | Retry in a moment |

## Not available yet

These are refused or missing today. Don't build around them.

| What | What you get today |
|---|---|
| Stop-limit orders | `400 unsupported_order_type` on futures |
| Limit, stop or bracket orders for options and stocks | `400 only_market_orders`, or `400 bad_request` for the bracket fields |
| Changing the size of a resting order | No route. Cancel it and place a new one |
| Adding to an open options or stock position | `409 duplicate_position`. Add to it in Nexus |
| Selling options to open | `400 bad_request` |
| Trading a challenge entry, or cancelling or moving its orders | `422 account_not_tradable`. Reads work |
| Futures in `GET /api/nexus/v1/trades` | Not listed. Use the account read and the order history |
| A push feed of fills or positions | No route. Poll the reads, inside the rate limits |
| Creating, resetting or buying accounts | No route. Do it in Nexus |
| Futures market data (quotes, bars, levels) | Not served here, or by the market data API or MCP server |
| Placing trades from the MCP server | Not yet. Use this API from your own code |
| Sending trades to a broker | Never. This API is for paper trades inside Nexus only |

> **Note:** Nexus is an educational beta for reviewing your own trading. Paper trades aren't financial advice, and nothing here is a recommendation to buy or sell anything.
