# CrossTrade API — AI Coding Assistant Rules > Drop this file into the root of your project (next to `package.json` / `pyproject.toml`). > Claude Code, Cursor (when reading `CLAUDE.md`), Aider, and Continue will pick it up automatically. You are working in a project that calls the CrossTrade API. Read these rules before generating any code that hits the API. ## The most important rule The base path is **`/v1/api/...`**. There is no `/api/...` prefix. There is no `/api/v1/...`. If you guess `/api/orders` or `/api/order` you will get a 404 — actually you'll get a structured 404 with a `did_you_mean` field, but you'll have wasted a round trip. When in doubt, fetch the catalog first: ``` GET https://app.crosstrade.io/v1/api/_endpoints ``` Or the full reference: ``` https://app.crosstrade.io/v1/api/llms-full.txt ``` Or the OpenAPI spec: ``` https://app.crosstrade.io/v1/api/openapi.json ``` ## Base URL ``` https://app.crosstrade.io ``` ## Authentication Every request requires a Bearer token: ``` Authorization: Bearer ``` Tokens are issued at https://app.crosstrade.io/user/my-account and require an active Pro subscription. Default to reading the token from the `XTRADE_TOKEN` environment variable. Don't hardcode it. ## Path conventions Endpoints are RPC-style PascalCase actions mapped to nested resource paths: - `PlaceOrder` → `POST /v1/api/accounts/{account}/orders/place` - `CancelOrder` → `POST /v1/api/accounts/{account}/orders/{id}/cancel` - `Flatten` → `POST /v1/api/accounts/{account}/positions/flatten` - `GetQuote` → `GET /v1/api/market/quote?instrument=ES%2003-26` - `GetBars` → `POST /v1/api/market/bars` (POST because the body has many fields) - `ListAccounts` → `GET /v1/api/accounts` Do **not** invent paths like `/v1/api/orders` (no trailing collection — orders are nested under an account) or `/v1/api/order` (singular). When unsure, hit `/v1/api/_endpoints`. ## Response shape Every response carries a boolean `success`: ```json { "success": true, "data": { ... } } ``` Errors: ```json { "success": false, "error": "invalid_account", "detail": "Account Sim999 not found" } ``` Some endpoints return `success: true` with a `warning` for non-fatal edge cases (e.g. flatten with no positions). Check it. The status keys are `success: true|false`, never `ok`, `passed`, `okay`, or `result`. ## Instrument format Anywhere you pass an `instrument`, all of these are accepted: - `"ES 03-26"` — full dated form (always works; prefer this in generated code) - `"ES"` — root symbol, resolves to current front month - `"ES1!"` — TradingView continuous format - `"ESH26"` — CME code ## Rate limits - 180 requests/minute per user, shared with WebSocket - 20-request burst - If exceeded: HTTP 429 with `{"error": "Rate limit exceeded"}` If you generate code that loops over many accounts or instruments, add a small `await asyncio.sleep(0.05)` (or equivalent) between calls — burst-friendly but doesn't pile up. ## Common gotchas - The user's NinjaTrader 8 must be running with the CrossTrade add-on connected. If not, calls return HTTP 408 `{"success": false, "error": "Client not ready..."}`. Always handle this case. - POST and PUT bodies are JSON; the API does not accept form-encoded bodies. - `account` is case-insensitive on input; the API echoes the canonical case. - For market data calls that take an `instrument`, also accept a `root` parameter as a fallback (resolves to front month). - The path parameter for orders is `{id}`, not `{orderId}` — but the body field for change/replace operations *is* `orderId`. ## Canonical examples ### Python setup for examples ```python import os import requests BASE_URL = "https://app.crosstrade.io" TOKEN = os.environ["XTRADE_TOKEN"] HEADERS = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } ``` ### Place a market order ```python response = requests.post( f"{BASE_URL}/v1/api/accounts/Sim101/orders/place", headers=HEADERS, json={"instrument": "ES 03-26", "action": "Buy", "quantity": 1, "orderType": "Market"}, ).json() if not response.get("success"): raise RuntimeError(response) ``` ```bash curl -X POST -H "Authorization: Bearer $XTRADE_TOKEN" -H "Content-Type: application/json" \ -d '{"instrument":"ES 03-26","action":"Buy","quantity":1,"orderType":"Market"}' \ https://app.crosstrade.io/v1/api/accounts/Sim101/orders/place ``` ### Place a bracketed entry (atomic cancel + place) ```python response = requests.post( f"{BASE_URL}/v1/api/accounts/Sim101/orders/cancel_and_bracket", headers=HEADERS, json={ "instrument": "ES 03-26", "action": "Sell", "quantity": 1, "takeProfit": 4550.0, "stopLoss": 4450.0, "ocoId": "protect_long_001", }, ).json() if not response.get("success"): raise RuntimeError(response) ``` ### Flatten everything across all accounts ```python response = requests.post( f"{BASE_URL}/v1/api/positions/flatten", headers=HEADERS, ).json() if not response.get("success"): raise RuntimeError(response) ``` ### Get historical bars ```python response = requests.post( f"{BASE_URL}/v1/api/market/bars", headers=HEADERS, json={"instrument": "ES 03-26", "periodType": "minute", "period": 5, "limit": 500}, ).json() if not response.get("success"): raise RuntimeError(response) ``` ### Subscribe to live quotes (WebSocket) ```python import websockets, json async with websockets.connect( "wss://app.crosstrade.io/ws/stream", extra_headers={"Authorization": f"Bearer {TOKEN}"}, ) as ws: await ws.send(json.dumps({"action": "subscribe", "instruments": ["ES 03-26"]})) async for msg in ws: print(json.loads(msg)) ``` ## When you don't know an endpoint 1. Hit `https://app.crosstrade.io/v1/api/_endpoints` — JSON catalog with every public route. 2. Or paste the URL `https://app.crosstrade.io/v1/api/llms-full.txt` into your prompt — it's the full single-file reference. 3. Or read the OpenAPI spec at `https://app.crosstrade.io/v1/api/openapi.json`. 4. Or browse the human docs at https://crosstrade.io/docs/api/overview. The 404 response on a wrong path also includes `did_you_mean` and links to all of the above. Read it.