schwab-sdk
Typed Rust client for the Charles Schwab Trader API and its streamer WebSocket.
- REST endpoints for accounts, orders, transactions, user preferences, and the full market-data surface (quotes, price history, option chains, instruments, market hours, movers).
- A streaming client for the Schwab streamer with typed payloads for the level-one, book, chart, screener, and account-activity services.
- All money and quantity fields use
rust_decimal::Decimal; secrets are wrapped insecrecynewtypes that redact inDebugand zeroize on drop.
What this crate does not do:
- It does not perform the OAuth authorization flow. Bring your own access token; see Schwab's developer portal for the auth-code / refresh-token flow.
- It does not retry failed requests. Each
Errorexposesis_retryableandretry_afterso callers can layer a policy of their choice (e.g.backon) on top. - It does not rate-limit. Schwab does not publish per-endpoint limits at the time of writing; callers are responsible for staying within them.
API documentation lives at docs.rs/schwab-sdk.
Design
-
Building blocks without policy. Every REST endpoint and streamer service is typed. Credential storage, OAuth, retries, rate limits, reconnect, and any strategy logic are the caller's responsibility. Extension points are exposed as seams rather than prescribing a default.
TokenProviderenables implementing your own auth policy.ConnectionEventcan be subscribed to for handling streamer reconnects as desired. -
Spec as written and forward-compatible. Field names, request shapes, and enum values follow Schwab's schema. Public enums and response structs are
#[non_exhaustive]and every enum carries anUnknown/Rawfallback so a new Schwab discriminant or service is non-breaking. -
Structured errors. One
thiserrorenum surfaces every failure and preserves both Schwab error envelopes (Trader and Market Data).Error::is_retryableandError::retry_afterclassify failures so callers can layerbackonor another policy on top. -
Round-trip tests. Every request and response type has serialization round-trip coverage. The streamer frame parser is tested against captured frames. No live Schwab session is required for
cargo test.
Usage
Resolve an account, read a quote, and place an order against it:
use dec;
use ;
use QuoteEntry;
use OrderRequest;
async
Stream live level-one quotes. The write half sends commands (log in first);
the read half yields one typed frame per recv:
use ;
use DataContent;
use Field;
async
Authentication and token rotation
SchwabClient reads its bearer through a TokenProvider trait. The SDK
consults it once per REST request and once per streamer LOGIN frame, so a
token rotated in the provider is observed on the next call without
rebuilding the client.
Static token
For a short-lived token where the application tears down the client when
it expires, use SchwabClient::new. It wraps the supplied AuthToken
in a StaticTokenProvider internally:
use ;
let client = new;
Rotating token
For long-lived clients, implement TokenProvider over whatever cell or
refresh strategy fits your application. The example below is a swappable
provider built on arc-swap for wait-free reads: a refresh loop calls
rotate when a new access token arrives, and the next REST call (or
streamer LOGIN) hands it out.
use Arc;
use ArcSwap;
use async_trait;
use ;
;
let provider = new;
let client = with_token_provider;
// Later, after your refresh strategy obtains a new token:
provider.rotate;
The SDK ships only StaticTokenProvider; refreshing providers,
persistence backends, and scheduling are application concerns.
Retries and idempotency
Error::is_retryable and Error::retry_after classify a failure so you can
layer a backoff policy on top. Read-only and naturally idempotent requests
(quotes, account reads, order lists, cancels) can be retried directly on a
retryable error.
Order placement is not retry-safe. Schwab's Trader API has no
client-supplied idempotency key, so placing an order is not safe to retry.
If a place call fails after the request reached Schwab (a timeout, a
dropped connection, a 5xx), the order may have been accepted even though you
received an Err. There is no key you can resend to deduplicate it.
The recovery pattern is to reconcile before deciding whether to resubmit:
- Record the time just before calling
place. - If
placereturns a retryable error, list the orders entered since that time withclient.orders(account_hash).list(from, to). - Match the returned orders by symbol, side, and quantity. If one matches, the order landed - adopt its id. If none does, it is safe to resubmit.
The same applies to replace, which Schwab implements as a cancel-and-place.
Security
schwab-sdk is built to reduce the risk of credential or PII leakage
through this crate; it is not a security boundary for the application as
a whole. The crate ships under MIT / Apache-2.0 with no warranty.
- Bearer tokens, customer ids, and account identifiers are
secrecy-backed newtypes that redact inDebugand zeroise onDrop. The raw value is reachable only via.expose_secret(), the single grep-able boundary. - The crate has no
println!,tracing, orlogcalls in production code; it never writes to disk; and noErrorvariant carries a bearer. A bearer is materialised only on theAuthorizationheader and in the streamer LOGIN frame. - REST and the streamer default to HTTPS / WSS. Release builds reject
any other scheme on base-URL overrides; debug builds permit
http://andws://so local fixture servers can be wired up in tests. Production deployments must use release builds.
See the secrets module docs for the full threat model
and caller-side hardening guidance (token storage, process exposure,
logging discipline, account-number vs. account-hash handling). See
SECURITY.md for the vulnerability-reporting channel
and the formal scope.
License
Licensed under either of
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Disclaimer
This project is an independent, community-maintained client. It is not affiliated with, endorsed by, or sponsored by Charles Schwab & Co., Inc. "Schwab" and related marks are the property of their respective owners.
This software is provided "as is" without warranty of any kind. The authors and contributors are not responsible for any financial loss, missed trades, incorrect or duplicate orders, or other trading outcomes arising from use of this crate. You are solely responsible for the orders your code submits and for verifying its behavior before trading real money. See the MIT and Apache-2.0 license texts for the full warranty disclaimer.