Placing Orders Through the tastytrade API: Construction, Multi-Leg, and Error States

By Stax Team

A tastytrade order is a container with an order type, a time in force, a price, and an array of legs. Each leg carries an instrument type, a symbol, an action, and a quantity. Multi-leg spreads are submitted as one order with multiple legs, not as several orders. Three details cause most production bugs: price is expressed per unit rather than as an order total, debit and credit are a distinct concept from the price magnitude, and an HTTP success on submission means the order was accepted, not that it was filled.

Order placement is where an automation stops being a data pipeline and starts being a thing that spends money. The API surface is not complicated, but several of its conventions are easy to get subtly wrong in ways that produce a valid, accepted, executable order that does something other than what you intended. Those are the expensive bugs, because nothing errors.

This covers the wire format, multi-leg and complex order construction, what to do with the response, and the error states that matter for an unattended client.

The shape of an order

The order payload uses kebab-case field names. The top-level fields you will actually use are order-type, time-in-force, price, price-effect, and legs. Depending on order type you may also send stop-trigger, value for notional orders, and gtc-date when the time in force requires an expiry date.

Each entry in legs carries four fields:

instrument-type identifies the asset class, with values including Equity, Equity Option, Future, Future Option, and Cryptocurrency. symbol is the instrument identifier in tastytrade symbology, which for options is not the same string a human would type. quantity is contracts or shares. action is the position effect.

The action field is worth pausing on because the vocabulary is not uniform across asset classes. Equities and options use the four-verb form: Buy to Open, Buy to Close, Sell to Open, Sell to Close. Futures use plain Buy and Sell. A client that hardcodes the four-verb form and then adds futures support will construct legs the API rejects, and a client that maps its internal representation carelessly can submit Sell to Open where it meant Sell to Close. That second error does not fail. It opens a short position instead of closing a long one, and you find out from the position report.

Price is per unit, not per order

This is the single most consequential convention in the API and it is stated plainly in the SDK documentation: price is always per quantity, not the price for the entire order.

Consider a two-leg strangle submitted at a price of 1.25. With one contract per leg, the total credit is 1.25 multiplied by the contract multiplier. With two contracts per leg, the price field is still 1.25, and the total credit collected is doubled. The price does not change when quantity changes; it is a per-unit price that the quantity scales.

The failure mode is a client that computes a desired total and puts that number in the price field. On a one-contract order it works, which is the worst possible outcome because it validates the bug. Scale to five contracts and the order is now priced five times away from intent. Depending on direction that either never fills or fills immediately at a price you did not mean.

Write the multiplication out explicitly in your order builder, and assert on the expected total before submission rather than trusting the arithmetic to be obvious at 3:55pm.

Debit, credit, and the sign convention trap

The tastytrade platform uses an explicit credit and debit toggle, and the API carries price-effect as its own field with Debit and Credit values. The response object likewise reports price-effect alongside price.

Some SDKs abstract this. The Python SDK documentation states directly that rather than using an explicit credit and debit toggle like the platform, it assumes negative numbers are debits and positive ones are credits. So the same economic intent is expressed as a positive price plus a Debit effect in one representation, and as a negative price in another.

If you are moving between the raw API, an SDK, and your own internal order model, this is a conversion boundary and it deserves an explicit adapter with tests rather than an inline sign flip. A sign error here does not produce an invalid order. It produces a valid order with inverted economics, which will be accepted and may fill.

Dry run before you submit

The API provides an order dry run that calculates what an order would do to buying power and what fees it would incur, without placing it. The documented purpose is to power an order confirmation screen.

For an unattended client there is no confirmation screen, which makes dry run more useful rather than less. The response carries a buying power effect object with the change in margin requirement, the change in buying power, current and new buying power, the isolated order margin requirement, and the effect direction. It also carries a fee calculation with clearing fees and total fees.

That gives you a programmable pre-trade check. You can assert that the new buying power stays above a floor, that the margin impact matches what your position sizing expected, and that fees are within a sane band, and refuse to submit if any assertion fails. This is a genuine safety layer that costs one extra round trip, and it catches sizing errors before they become positions rather than after.

It is also where the post-PDT margin regime becomes concrete. The PDT rule elimination effective June 4, 2026 replaced day-trade counting with real-time intraday margin monitoring, which means buying power is a live figure that moves with your positions during the session. A dry run reads that live figure rather than an assumption cached at market open.

The trade-off is latency. A dry run doubles the round trips on the order path. For a strategy where an extra round trip is material, run dry run in testing and on sizing changes rather than on every order, and keep the local assertions regardless.

Multi-leg orders

A spread is one order with multiple legs. It is not multiple orders, and building it as multiple orders is a category error with real consequences: separately submitted legs can partially fill, leaving you with naked exposure you never intended to hold, and they are priced independently rather than as a package.

Construction is mechanical. Build each leg with its instrument type, symbol, action, and quantity, put them in the legs array, and set one price for the package. A short strangle is two legs both selling to open. A vertical is one leg opening long and one opening short at a different strike. The order carries the net price.

Two things to get right. First, the per-unit price rule applies to the package, so the price is the net per-unit debit or credit across the legs, not a sum of leg prices. Second, leg ratios are expressed through quantity, so a ratio spread is legs with differing quantities rather than a special order type.

Complex orders: OCO, OTO, and OTOCO

Multi-leg handles simultaneous execution. Complex orders handle conditional relationships between separate orders, and tastytrade supports three forms.

OCO is one-cancels-other. It attaches a profit target and a stop to an existing position; when either fills the other is cancelled. It has no entry order because the position already exists.

OTO is one-triggers-other. A trigger order acts as the entry, and on execution it creates one or more subsequent orders.

OTOCO combines both: a trigger order for entry, plus a take-profit and a stop-loss that are OCO-linked to each other. This is the bracket, and it is the structure most automation actually wants, because it establishes the exit conditions at the same moment it establishes the position rather than in a follow-up call that might not happen.

That last point is the real argument for complex orders over software-managed exits. An exit condition that lives only in your process disappears when your process does. A bracket submitted at entry rests at the broker and survives your client crashing, your host rebooting, and your network dropping. Given that an eight-hour IP block is a documented possibility on this API, the difference between broker-resident and software-resident exits is not academic.

One operational gotcha that will bite: cancelling a complex order requires the complex order cancellation call, not the ordinary order cancellation call. The ordinary call can delete individual components of a complex order, which means using the wrong one does not error, it just silently dismantles part of your bracket and leaves the rest live. A stop that was cancelled while a take-profit remains is a position with no downside protection and no error message anywhere.

Reading the response

The most important thing to internalise: a successful submission means accepted, not filled.

The response contains an order object with an id, a status, and a set of fields that describe what can still be done to it, including cancellable and editable booleans. Legs carry remaining-quantity and a fills collection. Other fields track lifecycle: in-flight-at, cancelled-at, updated-at, plus replacing-order-id and replaces-order-id for orders that were modified.

A newly submitted order commonly returns with a status of Received. It is not filled. It may never fill. Treating a 201 as a fill is how a client ends up believing it holds a position it does not hold, which then causes the exit logic to do something incoherent.

Persist the order id immediately, before doing anything else with the response. It is the only handle you have for cancelling, replacing, or reconciling later.

Partial fills need explicit handling. remaining-quantity on each leg is the authoritative number, not the quantity you submitted. Position sizing, exit orders, and risk calculations all need to key off what actually filled. A client that assumes full fills will attempt to close positions it does not hold in the size it thinks.

Tracking status without polling

Order status changes asynchronously. There are two ways to learn about it and one of them is much better.

Polling means repeatedly querying live orders. Note that the live orders call is misleadingly named: it returns not only live orders but also cancelled and filled ones from the past 24 hours. That is useful for reconciliation and it is a poor basis for a status loop, because polling frequently enough to be responsive means generating request volume against an API whose rate ceiling is not published.

The account streamer pushes status changes as they happen. It is the correct mechanism for order state, it removes the polling loop entirely, and it gets you fill notifications at the speed the broker can deliver them rather than at your poll interval. Maintaining a persistent stream alongside your order submission path is a natural fit for the way high-concurrency I/O works in a Node.js client, where a long-lived connection costs essentially nothing while it is idle.

Keep the query path anyway, for reconciliation on reconnect. A stream that was disconnected for ninety seconds has a ninety-second hole in it, and the only way to fill that hole is to ask.

Error states

Distinguish three categories, because they need different responses.

Malformed request. Missing required fields, an invalid instrument type, an action verb the asset class does not accept, a price that violates the tick grid. Options trade in defined price increments, and an order priced off-increment is invalid. Validate against tick size before submission rather than discovering it in a rejection; one open-source client does exactly this and fails before placement when tick size data is unavailable rather than submitting an invalid increment. These are bugs. Do not retry them.

Business rejection. The request is well-formed but the order cannot be accepted: insufficient buying power, a position or account restriction, a closed market. These are legitimate answers and they should be surfaced, logged with the full reason, and in most cases not retried. Insufficient buying power does not become sufficient because you asked again.

Ambiguous failure. This is the one that matters and the one most guides skip. Your client POSTs an order and the connection times out. You do not know whether the order was placed. The naive response is to resubmit, and the naive response can double your position.

The correct recovery is to query rather than resubmit. On any ambiguous outcome from a submission, stop, query live orders, and determine what actually exists before taking any further action. This is the single most valuable defensive habit in an order path, and it is the reason to attach your own client-side identifier to orders where the API permits it, so that reconciliation is a lookup rather than a guess based on timestamps and symbols.

Consider whether your client should be permitted to resubmit at all without human involvement. For many strategies, failing loudly and stopping is the better behaviour, because an order that failed ambiguously during a fast market is an order whose original premise may no longer hold.

Cancel and replace

Modifying an order is a replace: change the price on the order object and submit it against the existing order id, which produces a new order that replaces the old one. The response carries replacing-order-id and replaces-order-id so the chain is traceable.

The race condition is unavoidable and worth naming. Between deciding to replace and the replacement landing, the original can fill. Your client must handle the case where a replace fails because the target is no longer replaceable, and the cancellable and editable flags on the order object tell you whether the attempt is even valid at the moment you read them, which is not necessarily the moment the request arrives.

The honest limits

Correct order construction is necessary and nowhere near sufficient.

A perfectly formed order executes a bad decision exactly as reliably as a good one. Nothing in this article has any bearing on whether a strategy is worth automating.

A resting bracket is not a guarantee. A stop order sends an order when triggered; FINRA is explicit that a stop price is not a guaranteed execution price, and a stop-limit may never execute at all. Broker-resident exits survive your client dying, which is a real and meaningful benefit, and they do not survive a gap.

Dry run validates buying power and fees at the moment you ask. It does not validate that the order will fill, that the market will be there, or that your sizing logic is sound.

Sandbox validates that your orders are well-formed and that your client authenticates. It tells you nothing about fills, spreads, or liquidity.

And the API can change. Field names, order types, and endpoints are the vendor decision, not yours. Build the order layer so that swapping brokers is a contained change.

The control that actually bounds loss remains position sizing, which is upstream of every line of order-construction code. The divide-by-20 rule is deliberately blunt for that reason: available trading capital divided by twenty as the ceiling on any single position. It constrains the outcome rather than predicting it, and it keeps working on the day the order path does not.

How this maps to a self-hosted client

StaxInvesting is software, not signals. The platform is provisioned into the member's own cloud environment, with broker credentials held in that environment's variables rather than in any vendor database, and no vendor access to running member instances. Because the order path runs inside the member's own self-hosted deployment, orders are constructed and submitted from the member's infrastructure to the member's own connected brokerage account, under the member's own trade-scoped credentials. Withdrawal permissions are never requested.

StaxInvesting is not a broker-dealer or a registered investment adviser and does not place trades on behalf of members.

Frequently asked questions

How do I submit a multi-leg option order to tastytrade? As a single order containing multiple entries in the legs array, each with its own instrument type, symbol, action, and quantity, with one net price on the order. Submitting each leg as a separate order risks partial execution and independent pricing.

Is the tastytrade order price the total or per contract? Per unit. The documentation states price is always per quantity, not the price for the entire order. Quantity scales it.

What does a successful order response mean? That the order was accepted, not that it filled. A newly submitted order commonly returns a status of Received. Fill state is tracked through order status and leg remaining quantity.

What is an order dry run? A call that calculates an order's effect on buying power and its fees without placing it. It is intended for confirmation screens and is equally useful as an automated pre-trade check.

What is the difference between OCO, OTO, and OTOCO? OCO links a profit target and a stop for an existing position so that one cancels the other. OTO uses a trigger order that creates subsequent orders on execution. OTOCO combines both into a bracket with an entry and two OCO-linked exits.

How do I cancel a bracket order? With the complex order cancellation call. The ordinary order cancellation call deletes individual components, which can leave part of a bracket live without producing an error.

Field names, symbology, order types, and status definitions are specified in the tastytrade developer documentation at developer.tastytrade.com, which is the authority to build against. This article describes the model as documented at publication and is not a substitute for the specification.


Disclaimer: This article is educational content about software engineering and API integration. It is not investment advice, financial advice, tax advice, legal advice, or a recommendation to buy or sell any security, nor a recommendation of any strategy, order type, or position structure. Any instruments named are used solely to illustrate API mechanics. Options trading involves substantial risk of loss and is not suitable for all investors. Please read Characteristics and Risks of Standardized Options before trading options. Automated trading systems carry additional risks including software defects, order construction errors, network and connectivity failures, broker API changes, rate limiting, and outages that may prevent orders from being placed, modified, or cancelled. Stop orders do not guarantee an execution price and stop-limit orders may not execute at all. Past performance does not indicate future results, and no configuration, order type, position-sizing rule, or risk setting can guarantee a profit or prevent a loss.

StaxInvesting LLC sells self-hosted trading software. It is not a broker-dealer, investment adviser, or financial institution, and it does not manage accounts, hold member funds, place trades on behalf of members, or access member brokerage accounts. Members run the software in their own cloud environment, connect their own brokerage accounts under their own credentials, and are solely responsible for their configuration, their credential security, their compliance with third-party API terms of service, and every trade executed in their account. Third-party API details described here reflect publicly available documentation as of publication and are subject to change by the vendor without notice; always verify against current official documentation. Consult a qualified financial adviser and tax professional regarding your individual circumstances.