# Open a trade (options, stocks or futures)

`POST https://app.skylit.ai/api/nexus/v1/trades`

API: Nexus.

One route for every asset class. `assetClass` picks the book.

- **Options** are bought to open, at market. Send `contract` as shorthand (like `SPY 600C 10/16`), or `ticker`, `strike`, `direction` and `expiration`. Not both. 1 to 1,000 contracts.
- **Stocks** take `ticker`, `side` (`buy` or `sell`) and `quantity`, 1 to 100,000 shares, at market.
- **Futures** take `ticker` (a product like `NQ` for the front month, or one contract like `NQZ26`), `side`, `quantity` (1 to 100) and optionally `account`. A market order fills now. `orderType: "limit"` with `limitPrice`, or `"stop"` with `stopPrice`, rests until the market trades through it. Add `stopLoss`, `takeProfit` and `trailingStop` for a bracket on any order type: each leg is an order of its own, `held` while the entry rests and `working` once it fills. A resting order can wait on a quiet market. A market order needs a trade in the last 15 seconds to open or add.
- **reduceOnly** stops a sell from opening a short when a stop already took you flat a second earlier. With `reduceOnly: true` the order goes through only if it shrinks what you hold, checked when it books. Otherwise it answers `422 reduce_only_would_open` and nothing is placed. It works on a market order with no bracket.
- Futures go to your practice account unless `account` names another: `"evaluation"` or `"funded"` when you have exactly one running, or an account id from capabilities. Evaluation and funded accounts keep all their rules (daily loss, max loss, contract cap). Challenge entries can't be traded through the API.
- Add `"test": true` to check and price the order without placing it. See Test orders in the intro.

A new options or stock trade answers `201` with the trade. A futures order answers `201` with the order and its bracket. A repeated `clientOrderId` answers `200` with the first one.

## Authentication

Send your Skylit API key as a bearer token: `Authorization: Bearer <key>`. No other header is accepted.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | Your `clientOrderId`, as a header instead of a body field. If you send both they must match. |

## Request body

`application/json`

```json
{
  "assetClass": "options",
  "contract": "string",
  "ticker": "string",
  "direction": "call",
  "strike": 0,
  "expiration": "string",
  "side": "buy",
  "quantity": 0,
  "price": 0,
  "ifOutside": "reject",
  "orderType": "market",
  "limitPrice": 0,
  "stopPrice": 0,
  "stopLoss": 0,
  "takeProfit": 0,
  "trailingStop": false,
  "reduceOnly": false,
  "account": "string",
  "clientOrderId": "string",
  "test": false
}
```

## Example request

```bash
curl "https://app.skylit.ai/api/nexus/v1/trades" \
  -H "Authorization: Bearer $SKYLIT_API_KEY"
```

## Responses

### 200

A repeated `clientOrderId`: the first order, with `meta.idempotentReplay: true`. Nothing new was placed.

Shape (placeholder values):

```json
{
  "data": {
    "id": "string",
    "userId": "string",
    "ticker": "string",
    "strike": 0,
    "expiration": "string",
    "direction": "call",
    "assetClass": "options",
    "averageEntryPrice": 0,
    "currentPrice": 0,
    "totalQuantity": 0,
    "remainingQuantity": 0,
    "status": "active",
    "entryDate": "string",
    "exitDate": "string",
    "exits": [
      {
        "id": "string",
        "price": 0,
        "quantity": 0,
        "date": "string",
        "type": "partial",
        "orderType": "string"
      }
    ],
    "source": "string",
    "test": false
  },
  "meta": {
    "fill": {},
    "clientOrderId": "string",
    "idempotentReplay": false,
    "test": false,
    "notChecked": [
      "string"
    ],
    "discordRouting": {},
    "nothingToClose": false,
    "stillOpen": [
      "string"
    ]
  }
}
```

### 201

Filled (or, for a futures limit or stop order, resting). Options and stocks answer a `Trade`, futures a `FuturesOrder`. A test order is the same shape with `"test": true`.

The options trade, filled at the live ask.:

```json
{
  "data": {
    "id": "6f1c2a4e-8b1d-4c39-9a51-2d7e0b3c9f10",
    "userId": "0b8e5d2c-41a7-4f6e-9c3b-7a1d2e4f5a60",
    "ticker": "SPY",
    "strike": 600,
    "expiration": "2026-10-16",
    "direction": "call",
    "assetClass": "options",
    "tradeType": "simulated",
    "executionStatus": "simulated",
    "averageEntryPrice": 1.84,
    "totalQuantity": 2,
    "remainingQuantity": 2,
    "status": "active",
    "entryDate": "2026-10-09T14:32:05Z",
    "source": "api",
    "createdAt": "2026-10-09T14:32:05Z",
    "updatedAt": "2026-10-09T14:32:05Z"
  },
  "meta": {
    "fill": {
      "price": 1.84,
      "priceSource": "market"
    },
    "clientOrderId": "bot-0142"
  }
}
```

Your price was inside the window, so it was taken. bid and ask are the quote it was checked against.:

```json
{
  "data": {
    "id": "29d0e1f2-a3b4-4c5d-8e6f-809102132435",
    "userId": "0b8e5d2c-41a7-4f6e-9c3b-7a1d2e4f5a60",
    "ticker": "SPY",
    "strike": 600,
    "expiration": "2026-10-16",
    "direction": "call",
    "assetClass": "options",
    "tradeType": "simulated",
    "executionStatus": "simulated",
    "averageEntryPrice": 1.84,
    "totalQuantity": 2,
    "remainingQuantity": 2,
    "status": "active",
    "entryDate": "2026-10-09T14:32:05Z",
    "source": "api"
  },
  "meta": {
    "fill": {
      "price": 1.84,
      "priceSource": "client",
      "bid": 1.82,
      "ask": 1.86
    }
  }
}
```

A stock buy is a long: direction call, strike 0, today's date.:

```json
{
  "data": {
    "id": "3ae1f2a3-b4c5-4d6e-9f80-910213243546",
    "userId": "0b8e5d2c-41a7-4f6e-9c3b-7a1d2e4f5a60",
    "ticker": "AAPL",
    "strike": 0,
    "expiration": "2026-10-09",
    "direction": "call",
    "assetClass": "stocks",
    "tradeType": "simulated",
    "executionStatus": "simulated",
    "averageEntryPrice": 254.31,
    "totalQuantity": 50,
    "remainingQuantity": 50,
    "status": "active",
    "entryDate": "2026-10-09T14:33:40Z",
    "source": "api"
  },
  "meta": {
    "fill": {
      "price": 254.31,
      "priceSource": "market"
    }
  }
}
```

The futures order, filled at the last trade plus one tick.:

```json
{
  "data": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "assetClass": "futures",
    "source": "api",
    "account": {
      "id": "3c2b1a90-5e4d-4f3a-8b2c-1d0e9f8a7b6c",
      "kind": "practice",
      "name": "Paper"
    },
    "symbol": "NQZ26",
    "side": "buy",
    "quantity": 1,
    "role": "entry",
    "orderType": "market",
    "status": "filled",
    "fillPrice": 25312.25,
    "placedAt": "2026-10-09T14:35:10Z",
    "filledAt": "2026-10-09T14:35:10Z"
  },
  "meta": {
    "fill": {
      "price": 25312.25,
      "priceSource": "market"
    },
    "clientOrderId": "bot-0143"
  }
}
```

The limit order resting, with its stop and target held until it fills.:

```json
{
  "data": {
    "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
    "assetClass": "futures",
    "source": "api",
    "account": {
      "id": "8d7c6b5a-4e3f-4a2b-9c1d-0e1f2a3b4c5d",
      "kind": "evaluation",
      "name": "50K Evaluation"
    },
    "symbol": "NQZ26",
    "side": "buy",
    "quantity": 1,
    "role": "entry",
    "orderType": "limit",
    "status": "working",
    "limitPrice": 25300,
    "placedAt": "2026-10-09T14:36:00Z",
    "bracket": [
      {
        "id": "c3d4e5f6-a7b8-4c9d-8e0f-2a3b4c5d6e7f",
        "assetClass": "futures",
        "source": "api",
        "account": {
          "id": "8d7c6b5a-4e3f-4a2b-9c1d-0e1f2a3b4c5d",
          "kind": "evaluation",
          "name": "50K Evaluation"
        },
        "symbol": "NQZ26",
        "side": "sell",
        "quantity": 1,
        "role": "stop_loss",
        "orderType": "stop",
        "status": "held",
        "stopPrice": 25280,
        "placedAt": "2026-10-09T14:36:00Z",
        "parentId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
      },
      {
        "id": "d4e5f6a7-b8c9-4d0e-9f1a-3b4c5d6e7f80",
        "assetClass": "futures",
        "source": "api",
        "account": {
          "id": "8d7c6b5a-4e3f-4a2b-9c1d-0e1f2a3b4c5d",
          "kind": "evaluation",
          "name": "50K Evaluation"
        },
        "symbol": "NQZ26",
        "side": "sell",
        "quantity": 1,
        "role": "take_profit",
        "orderType": "limit",
        "status": "held",
        "limitPrice": 25340,
        "placedAt": "2026-10-09T14:36:00Z",
        "parentId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
      }
    ]
  }
}
```

It only took 1 off the long. Had it been bigger than the position, or on the same side, it would have answered 422 reduce_only_would_open.:

```json
{
  "data": {
    "id": "4bf2a3b4-c5d6-4e7f-8091-021324354657",
    "assetClass": "futures",
    "source": "api",
    "account": {
      "id": "8d7c6b5a-4e3f-4a2b-9c1d-0e1f2a3b4c5d",
      "kind": "evaluation",
      "name": "50K Evaluation"
    },
    "symbol": "NQZ26",
    "side": "sell",
    "quantity": 1,
    "role": "entry",
    "orderType": "market",
    "status": "filled",
    "fillPrice": 25318,
    "placedAt": "2026-10-09T14:52:31Z",
    "filledAt": "2026-10-09T14:52:31Z"
  },
  "meta": {
    "fill": {
      "price": 25318,
      "priceSource": "market"
    },
    "clientOrderId": "nq-trim-0001"
  }
}
```

A test trade. Nothing was placed: it lives in your test log.:

```json
{
  "data": {
    "id": "e5f6a7b8-c9d0-4e1f-8a2b-4c5d6e7f8091",
    "userId": "0b8e5d2c-41a7-4f6e-9c3b-7a1d2e4f5a60",
    "ticker": "SPY",
    "strike": 600,
    "expiration": "2026-10-16",
    "direction": "call",
    "assetClass": "options",
    "averageEntryPrice": 1.84,
    "currentPrice": 1.84,
    "totalQuantity": 2,
    "remainingQuantity": 2,
    "status": "active",
    "entryDate": "2026-10-09T14:40:12Z",
    "test": true
  },
  "meta": {
    "test": true,
    "fill": {
      "price": 1.84,
      "priceSource": "market"
    }
  }
}
```

### 400

The request was refused before anything was traded. Codes: `bad_request`, `bad_contract`, `invalid_quantity`, `invalid_price`, `unsupported_asset_class`, `unsupported_order_type`, `only_market_orders`, `test_orders_unavailable`, `test_modify_unavailable`.

A typo in the body.:

```json
{
  "error": {
    "code": "bad_request",
    "message": "Unknown field \"quantitty\". See the trades API docs for the fields you can send."
  }
}
```

### 401

No key, or a key that isn't valid. Codes: `missing_api_key`, `invalid_api_key`.

No key sent.:

```json
{
  "error": {
    "code": "missing_api_key",
    "message": "Send your Skylit API key in the Authorization header: Authorization: Bearer YOUR_SKYLIT_API_KEY."
  }
}
```

### 403

The key works but the account can't do this. Codes: `account_suspended`, `nexus_access_denied`, `futures_access_denied`.

The account doesn't include Nexus.:

```json
{
  "error": {
    "code": "nexus_access_denied",
    "message": "This Skylit account doesn't include Nexus, so it can't place Nexus trades."
  }
}
```

### 404

Not on your account. Codes: `trade_not_found`, `account_not_found`, `order_not_found`.

Not your trade, or no such trade.:

```json
{
  "error": {
    "code": "trade_not_found",
    "message": "No trade with that id on your account."
  }
}
```

### 409

The order clashes with what you hold or sent before. Codes: `duplicate_position` (with `tradeId`), `trade_closed`, `open_position_limit`, `client_order_id_used`, `request_in_progress`, `account_locked`, `account_busy`, `account_ended`, `order_not_working`.

You already hold this contract.:

```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": "6f1c2a4e-8b1d-4c39-9a51-2d7e0b3c9f10"
  }
}
```

### 422

Read fine, but the market or the account says no. Codes: `market_closed`, `contract_expired`, `contract_not_offered`, `price_outside_market`, `insufficient_buying_power`, `account_not_tradable`, `account_ambiguous`, `reduce_only_would_open`, `order_refused`.

Outside market hours.:

```json
{
  "error": {
    "code": "market_closed",
    "message": "The market is closed for this contract right now, so nothing was filled."
  }
}
```

### 429

Over a rate limit. Wait `Retry-After` seconds, then retry. Code: `rate_limited`.

Headers: `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`.

Too many writes from this key.:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests (limit 30). Slow down and retry in 12 seconds."
  }
}
```

### 500

Something went wrong on our side. If you sent a `clientOrderId`, retry with the same one. Code: `internal_error`.

Retry with the same clientOrderId.:

```json
{
  "error": {
    "code": "internal_error",
    "message": "Something went wrong on our side. If you sent a clientOrderId, retry with the same one (it can't fill twice). Otherwise check your account before retrying."
  }
}
```

### 503

Try again in a moment (honour `Retry-After` when it's sent). Codes: `stale_quote`, `no_live_price`, `rules_pending`, `unavailable`, `auth_unavailable`, `access_check_unavailable`, `api_keys_unavailable`, `trades_api_disabled`.

Headers: `Retry-After`.

No live price right now.:

```json
{
  "error": {
    "code": "no_live_price",
    "message": "There's no live price for this contract right now, so nothing was filled. Try again in a moment."
  }
}
```
