1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// The public API is the crate contract, and an undocumented item is a
// contract nobody can read. Denied at the crate root rather than in a
// Makefile target, so it fails at the same moment as any other compile
// error and cannot be forgotten by running a different command.
// A doc link that points nowhere is documentation that lies about where to
// look next, which is worse than not linking at all.
//! # tastytrade
//!
//! A Rust client for the tastytrade brokerage API. **Orders placed through it
//! move real money.**
//!
//! This is the API reference. The [README] is the tour: what the crate covers,
//! how to authenticate, and a worked example per area. What follows is the
//! handful of behaviours a caller has to know before reading any individual
//! method, because they are properties of the whole crate rather than of one
//! call.
//!
//! [README]: https://github.com/joaquinbejar/tastytrade#readme
//!
//! ## Certification is the default
//!
//! [`utils::config::TastyTradeConfig::from_env`] selects the **certification**
//! environment (`api.cert.tastyworks.com`). Production is a deliberate opt-in:
//!
//! ```shell
//! TASTYTRADE_USE_DEMO=false # production — orders placed here are real
//! ```
//!
//! Only a value that parses as `false` selects production. A missing, empty or
//! misspelled variable resolves to certification, so a typo cannot be what
//! points an order at a funded account.
//!
//! A session is bound to the deployment it authenticated against: it will not
//! present a certification token to production, and it will not send the client
//! secret to a host it did not authenticate with.
//!
//! ## Authentication is OAuth2, and only OAuth2
//!
//! tastytrade **decommissioned `POST /sessions` on 2026-02-11**. Username and
//! password authentication, session tokens and remember tokens are gone from
//! the venue and gone from this crate with it.
//!
//! ```rust,no_run
//! use tastytrade::TastyTrade;
//! use tastytrade::utils::config::TastyTradeConfig;
//!
//! # async fn connect() -> Result<(), Box<dyn std::error::Error>> {
//! let config = TastyTradeConfig::from_env();
//! let tasty = TastyTrade::connect(&config).await?;
//!
//! for account in tasty.accounts().await? {
//! // Redacted: doc examples get copied, and an account number in a log is
//! // the thing this crate spends most of its care avoiding.
//! println!("{}", account.number().redacted());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! Access tokens last about fifteen minutes and every request renews the one in
//! hand before it expires, so a long-lived client keeps working. A renewal is
//! never a *retry*: a `POST` that may have placed an order is not replayed on a
//! `401`.
//!
//! [`TastyTrade::connect_with_authorization_code`] is the third-party grant, for
//! an application acting on somebody else's account.
//!
//! ## Nothing that trades happens without a receipt
//!
//! Placement, replacement, editing and complex orders all go through the same
//! shape: dry-run, read what the venue said, then apply the receipt.
//!
//! ```rust,no_run
//! # use tastytrade::prelude::*;
//! # async fn place(account: &Account<'_>, order: &Order)
//! # -> Result<(), Box<dyn std::error::Error>> {
//! let receipt = account.review_order(order).await?;
//!
//! for warning in receipt.warnings() {
//! // Venue prose, written for a person: it can name the account or the
//! // buying power, so it belongs on a screen rather than in a log.
//! println!("{warning}");
//! }
//!
//! // `accept` refuses when there are warnings. That is not a refusal to
//! // proceed — it is a refusal to proceed *silently*.
//! let reviewed = receipt.accept()?;
//! account.place_reviewed_order(reviewed).await?;
//! # Ok(())
//! # }
//! ```
//!
//! A receipt binds the account number **and** the deployment, because
//! certification reuses production account numbering — without the origin, a
//! sandbox dry run would authorise a real order against the same number. No
//! receipt is `Clone`: duplicable proof is not proof.
//!
//! [`accounts::Account::place_order`] still exists for callers managing the
//! review themselves. It carries no evidence that one happened.
//!
//! ## Money is `Decimal`
//!
//! Every price, quantity, balance and ratio is [`rust_decimal::Decimal`].
//! `f64` appears in exactly one place — [`types::dxfeed`], where the streaming
//! feed imposes it — and REST paths never reuse those types even when the field
//! names match.
//!
//! ## An absent field is unknown, never zero
//!
//! A flag the venue did not send is `None`, not `false`; a price it did not send
//! is `None`, not `0`. Certification omits fields production sends, and
//! "we were not told whether this account is frozen" and "this account is not
//! frozen" are different facts — only one of them is safe to act on.
//!
//! ## Secrets never render themselves
//!
//! The client secret, the refresh and access tokens, the DXLink quote token, the
//! AI-search token and the whole customer resource print as `***` or as a field
//! count. Not in `Debug`, not in `Display`, not in a log line, not in an error
//! message — an error is a string the caller prints wherever they like.
//!
//! Account numbers are redacted from every request path that reaches an error,
//! and a response body is never logged at any level: an error document from an
//! endpoint this crate does not control can echo a credential.
//!
//! ## A library does not panic
//!
//! No `unwrap`, no `expect`, no unchecked indexing on any path reachable from a
//! public method. Everything fallible returns [`TastyTradeError`]. A local
//! failure is [`TastyTradeError::Precondition`] and reports `is_retryable()`
//! false, because nothing was sent.
//!
//! ## Unknown values survive
//!
//! [`api::base::Items`] skips an item it cannot decode rather than failing a
//! whole listing, so a strict enum on a response would make a row **disappear**
//! — silently. The response enums therefore keep an `Unknown(String)` arm that
//! round-trips the venue's text: a new order status, transaction kind or
//! instrument classification is visible and matchable instead of missing.
//!
//! Request enums are closed, for the opposite reason: tolerance there would only
//! let a caller send something the venue rejects.
//!
//! ## Cryptocurrency order routing is suspended
//!
//! tastytrade disabled it on 2026-06-29, until further notice. An order with a
//! cryptocurrency leg is refused locally on every routing path. **Instrument
//! discovery and market data are unaffected.** The whole decision is
//! [`prelude::CRYPTOCURRENCY_TRADING_ENABLED`], one constant.
//!
//! ## Streaming
//!
//! Two websockets, and they are different services. Market data is DXLink,
//! reached with a token from `GET /api-quote-tokens`; account notifications are
//! tastytrade's own streamer, authenticated with the access token. Both
//! reconnect under a [`streaming::reconnect::BackoffPolicy`] and expose
//! [`streaming::reconnect::ConnectionState`].
//!
//! Candles are the only route to a price series in this crate, and the only
//! subscription needing more than a symbol — a candle is addressed by a symbol
//! carrying its period, `AAPL{=5m}`.
//!
//! ```rust,no_run
//! # use chrono::{Duration, Utc};
//! # use tastytrade::{Symbol, TastyTrade};
//! # use tastytrade::dxfeed::{CandlePeriod, EventData, EventKind};
//! # async fn bars(tasty: &TastyTrade) -> Result<(), Box<dyn std::error::Error>> {
//! let mut streamer = tasty.create_quote_streamer().await?;
//! let mut bars = streamer.create_sub([EventKind::Candle]).await?;
//!
//! // `from_time` is required, not optional: without one a candle subscription
//! // replays an unbounded history.
//! bars.add_candles(
//! &[Symbol("AAPL".to_string())],
//! CandlePeriod::minutes(5)?,
//! Utc::now() - Duration::days(2),
//! )
//! .await?;
//!
//! if let Ok(event) = bars.get_event().await
//! && let EventData::Candle(candle) = event.data
//! {
//! println!("{}: o {} c {}", event.sym, candle.open, candle.close);
//! }
//! # Ok(())
//! # }
//! ```
//!
//! A subscription's buffer is bounded, so a slow consumer loses events rather
//! than stalling every other subscription.
//! [`streaming::quote_streamer::QuoteSubscription::lagged`] makes that
//! observable, and for candles it is recoverable across a reconnect: a dropped
//! bar stops the resume point advancing, so the next connection asks for it
//! again.
//!
//! The account websocket publishes a **full object** on every change — never a
//! diff. The fills inside an order's legs are the only place an executed price
//! reaches this crate; no REST endpoint returns one. Anything that is JSON
//! reaches the caller, including a `type` nobody here recognises.
//!
//! ## Where to look
//!
//! [`prelude`] re-exports the advertised surface in one import. The endpoint
//! groups hang off [`TastyTrade`] and [`accounts::Account`]; the filters that
//! narrow them are `*Filter` types taking a [`api::query::PageRequest`].
/// Compiles every Rust block in `README.md` as a doc test.
///
/// The README is hand-written now, which means nothing else checks its
/// examples — and a README whose code does not compile is worse than one with
/// no code at all. This couples the *code* to the crate without coupling the
/// *prose*: the file is still authored by hand, and `src/lib.rs` no longer
/// generates it.
///
/// `#[cfg(doctest)]` keeps the item out of every build except the doc-test
/// pass, so it costs nothing at compile time and appears in no documentation.
;
/// REST surface: authentication, accounts, instruments and option chains.
/// Real-time transports: DXLink quotes and the account websocket.
/// The commonly used types in one import.
/// Configuration, logging, bulk downloads and parsing helpers.
pub use accounts;
pub use TastyResult;
pub use TastyTrade;
pub use ;
pub use dxfeed;
pub use InstrumentType;
pub use oauth;
pub use ;
pub use ;
pub use ;
pub use ;