Tradovate API

Tradovate API Bracket (OSO/OCO) Orders Fail

Your entry fills on ES or NQ and the stop-loss or take-profit never shows up. Here's what the 200 OK, the 404, and the 'Wrong OCO combination' reject actually mean, and how to fix each one.

Reviewed by the PickMyTrade Trading Systems Team Last updated
· 8 min read
Tradovate API bracket order response showing HTTP 200 OK with failureReason and failureText in the body

Your automation fires a Tradovate API bracket order, the entry fills on ES or NQ, and then… nothing. No stop-loss, no take-profit. You're now one bad tick away from an uncapped position, and your log says the order “sent fine.” That's the trap. In 2026 the classic failure isn't a red error screen, it's a 200 OK HTTP status hiding a failureReason and failureText in the body, or an orderStrategy call that comes back as a bare 404. The variants you'll hit most: a 404 (undocumented) from startOrderStrategy, an Invalid JSON parse error, a Wrong OCO combination reject, and the nastiest one, a silent half-fill where only the entry survives. Here's what each error actually means, how to fix the endpoint and field problems behind them, and how PickMyTrade keeps an unprotected fill from ever reaching your account.

Quick Checklist for Failing Bracket (OSO/OCO) Orders

  • Read the response body, not just the HTTP code. Tradovate hands you 200 OK even when the order is dead on arrival. The truth lives in failureReason / failureText.
  • Confirm the endpoint. Use order/placeOSO for an entry that sends stop/target legs, order/placeOCO for two linked exits, and orderStrategy/startOrderStrategy for a server-side bracket.
  • Send both account fields. You need the numeric accountId (from account/list) and the accountSpec account name. Miss one, or mismatch them, and you get Access is denied.
  • Serialize real JSON. Post a JSON body (json= in Python requests), not a raw dict, otherwise it's Invalid JSON: expected '{'.
  • Use orderStrategyTypeId: 2. It's the only valid value. A 1 gets you a 404 (undocumented).
  • Keep a fallback ready. If the strategy endpoint keeps failing, place the entry, then attach a stop/target placeOCO once you're holding the position.

What “Wrong OCO Combination” and the 404 Errors Mean

Tradovate gives you several ways to submit protected orders, and each one fails in its own way. A bracket is an entry order plus two child legs, a protective stop and a profit target, where filling or cancelling one child cancels the other. On the REST API you build that with order/placeOSO (One-Sends-Other): a parent order carries bracket1 and bracket2 objects that only wake up after the parent fills. order/placeOCO (One-Cancels-Other) links two orders so a fill on one cancels the other, handy for bolting a stop and target onto a position you already hold.

The confusing part: these endpoints happily return 200 OK while still rejecting the order. Tradovate reports business-logic failures inside the response body, not in the HTTP status. So a payload that reaches the server cleanly can still come back with {"failureReason": "InvalidPrice", "failureText": "Wrong OCO combination"}, meaning the price levels or order-type pairing you chose aren't a legal OCO. A stop and a limit sitting on the wrong side of the market will do it. Treat any body containing failureReason as a rejection, full stop, no matter what the status line says.

The orderStrategy/startOrderStrategy endpoint is a different animal. It runs a server-side bracket strategy, the API equivalent of an ATM strategy, and it's far pickier about its payload. Send the wrong orderStrategyTypeId, or call it over plain REST when it wants a WebSocket frame, and you get an unhelpful 404 (undocumented). It looks like a missing route, but it's really a rejected request.

Top Causes of Tradovate API Bracket Orders Failing

1. You only checked the HTTP status, not the body

This is the number-one trap. Your client sees 200 OK, logs “order sent,” and walks away, but the entry was rejected, or only the parent order made it through. Parse every OSO/OCO response for failureReason and failureText before you call the bracket live.

2. Wrong orderStrategyTypeId on startOrderStrategy

A lot of people try orderStrategyTypeId: 1 and get a 404 (undocumented) for their trouble. There's only one valid value, and it's 2. Anything else is rejected before the strategy is ever built.

Tradovate API startOrderStrategy request highlighting the orderStrategyTypeId field set to 2

3. Calling startOrderStrategy over REST instead of WebSocket

Even with the right type id, this endpoint is unreliable over plain HTTP REST. It's built to run over a WebSocket, and it works reliably when you drive it that way. A REST call that keeps 404-ing will often succeed, unchanged, the moment you send it as a WebSocket frame.

4. Malformed JSON or a raw dict instead of a JSON body

A common placeOCO failure is Invalid JSON: expected '{', offset: 0x00000075. That happens when the request ships a form-encoded dictionary instead of a serialized JSON string, in Python, passing data=payload instead of json=payload (or forgetting json.dumps()). The server never sees valid JSON, so it throws out the whole bracket.

5. Missing accountSpec, wrong accountId, or an illegal price combination

Bracket endpoints want both the numeric accountId (pulled from account/list) and the string accountSpec (your account name). Drop either one and you're looking at Access is denied. Separately, placeOCO and placeOSO check that the two legs form a legal combination, a stop and limit priced on the wrong side of the market come back as Wrong OCO combination. Market-entry brackets add one more wrinkle: because the API needs concrete price levels for the child legs, you generally can't attach tick-relative TP/SL to a plain Market entry through placeOSO. You either compute the price levels yourself, or let the strategy engine handle the offsets.

How to Fix Tradovate API Bracket Orders: Step-by-Step

Fixing the “200 OK but no legs” trap

  • After every placeOSO / placeOCO / startOrderStrategy call, parse the JSON body.
  • Check for a failureReason key. If it's there, treat the order as rejected and read failureText for the cause.
  • Only mark the bracket live once you've got confirmed order IDs for the parent and both child legs, poll order/list or subscribe to order events over WebSocket.
  • Log the full body, not the status code, so half-fills show up in your audit trail.

Fixing startOrderStrategy 404s

  • Set orderStrategyTypeId to 2, the only value it accepts.
  • Send both accountId and accountSpec, plus symbol, action, and the params object (with your entryVersion and brackets).
  • Submit the request as a WebSocket frame rather than a REST POST. This endpoint is designed to run over the WebSocket, and the frame format it expects is well documented.
  • Retry the identical payload over the socket before you assume your JSON is broken, the 404 is usually transport, not schema.
Tradovate API placeOSO payload showing bracket1 stop-loss and bracket2 take-profit legs with accountId and accountSpec

Fixing Invalid JSON and Wrong OCO combination

  • Serialize the payload as JSON. In Python requests, use requests.post(url, headers=..., json=payload), never data=payload with a dict.
  • Confirm the Content-Type: application/json header is set.
  • For placeOCO, check the geometry of the two legs: the stop and the limit have to sit on opposite, valid sides of the current price for the position's direction. If you see Wrong OCO combination, swap the leg that's on the wrong side.
  • For a Market entry, either wait for the fill and attach a placeOCO stop/target to the resulting position, or switch to startOrderStrategy, which accepts relative offsets.

The reliable fallback: separate entry + OCO

When a native bracket just won't cooperate, decouple the entry from the exits. Place the entry order on its own with order/placeOrder, confirm the fill, then submit a placeOCO that pairs the protective stop with the profit target against the now-open position. It's one extra round trip, but it sidesteps the strategy endpoint entirely and gives you explicit control over each leg.

Tradovate API fallback flow: a filled entry order followed by a placeOCO attaching stop and target legs

Troubleshooting Table

Error Meaning Fix
200 OK with failureReason in bodyOrder rejected at the business layer despite a clean HTTP statusParse the body; never trust the status code alone
404 (undocumented) on startOrderStrategyWrong orderStrategyTypeId, or REST used where WebSocket is expectedSet orderStrategyTypeId: 2 and submit over WebSocket
Invalid JSON: expected '{', offset: 0x...A raw dict / form body was sent instead of serialized JSONPost a JSON body (json= in requests) or json.dumps() first
failureReason: InvalidPrice, failureText: Wrong OCO combinationThe two OCO legs are not a legal price/type pairingReprice so stop and limit sit on valid opposite sides
Access is deniedMissing/incorrect accountId or accountSpecSend the numeric accountId from account/list plus the accountSpec name
Entry fills, no stop/target appearChild legs silently rejected while parent acceptedConfirm all three order IDs, or fall back to entry + placeOCO

Prevent This with PickMyTrade

Hand-rolling OSO/OCO payloads means you own every one of the failure modes above. PickMyTrade sits between your TradingView alerts and Tradovate and handles the bracket plumbing for you:

  • Bracket & OCO Handling, attaches stop-loss and take-profit legs correctly, so an entry never lands unprotected.
  • Response Validation, inspects the full order response, not just the HTTP status, and surfaces real rejections instead of a false “success.”
  • Entitlement & Risk Filters, respects your account's data-agreement state and prop-firm limits before an order is routed.
  • Rate-Limit-Safe Routing, spaces out order flow so bracket legs don't bounce off request caps.
  • Multi-Account Sync, mirrors the same protected bracket across every connected account.

Trade Rejection-Free

Start your free 5-day trial, link your alerts today and let PickMyTrade attach stop-loss and take-profit correctly every time.

Start Your Free 5-Day Trial

Frequently Asked Questions

Use order/placeOSO when a single entry should send a stop and target as bracket1 and bracket2. Use order/placeOCO to link two exit orders so filling one cancels the other. Use orderStrategy/startOrderStrategy for a server-side bracket strategy with relative offsets.

Tradovate reports business-logic rejections inside the response body, not in the HTTP status. Parse the body for failureReason and failureText, and only treat the bracket as live once you have confirmed order IDs for the parent and both child legs.

It's an InvalidPrice rejection meaning your two OCO legs aren't a legal pairing, typically the stop and limit are priced on the wrong side of the market for the position's direction. Reprice the offending leg.

Almost always a wrong orderStrategyTypeId (it must be 2) or a REST call where Tradovate expects a WebSocket frame. Fix the type id and resend over the WebSocket.

You're sending a raw dictionary or form-encoded body. Serialize the payload to JSON first, in Python requests, pass json=payload instead of data=payload.

Not directly, placeOSO expects concrete price levels for its child legs. Either compute the exact prices before submitting, wait for the fill and attach a placeOCO, or use startOrderStrategy, which accepts relative offsets.

Decouple the flow. Place the entry with order/placeOrder, confirm the fill, then submit a placeOCO that pairs the stop and target against the open position. It avoids the strategy endpoint entirely.

Yes. Bracket endpoints expect the numeric accountId from account/list and the string accountSpec account name. Omitting either returns Access is denied.

This guide is for educational and informational purposes only and is not financial, investment, or trading advice. Trading futures and other leveraged products carries a substantial risk of loss and is not suitable for every investor. PickMyTrade is an independent third-party automation platform and is not affiliated with, endorsed by, or sponsored by Tradovate, Inc. or Bookmap. All related names, logos, and trademarks are the property of their respective owners. Platform features and steps change over time, so always confirm the current process in the official platform documentation before acting.