Skip to main content

Crate schwab_sdk

Crate schwab_sdk 

Source
Expand description

A typed Rust client for the Charles Schwab Trader API, Market Data APIs, and streaming data.

It provides access to every endpoint via a namespace accessor on SchwabClient. With it you can:

All money and quantity fields use rust_decimal::Decimal. Secrets (AuthToken, CustomerId, AccountNumber, AccountHash) are wrapped in newtypes that redact in Debug and zeroize on Drop.

To start, you will need to obtain an access token. See the Authentication section for details.

The client makes it simple to access the Schwab API through a fluent interface.

use schwab_sdk::{AuthToken, SchwabClient};

// Construct an access token from an environment variable and create a
// client.
let token = AuthToken::new(std::env::var("SCHWAB_ACCESS_TOKEN").unwrap());
let client = SchwabClient::new(token);

// Retrieve a list of linked accounts and their account numbers.
let accounts = client.accounts().numbers().await?;
println!("{} linked account(s)", accounts.len());

§Read a quote and place an order

This example requests a quote for AAPL, places a limit buy order just under the last trade, and prints the order id. Orders are constructed using the orders::OrderRequest builder, which enforces type safety and compile-time validation.

use rust_decimal_macros::dec;
use schwab_sdk::{AuthToken, SchwabClient};
use schwab_sdk::market_data::QuoteEntry;
use schwab_sdk::orders::OrderRequest;

let client = SchwabClient::new(AuthToken::new("token"));

// 1. Resolve the encrypted account hash. Every per-account endpoint
//    takes this hash in its `{accountNumber}` path segment, never the
//    plain account number.
let accounts = client.accounts().numbers().await?;
let account = accounts.first().expect("at least one linked account");
let account_hash = &account.hash_value;

// 2. Read a quote and pull the last price out of the typed entry. An
//    unknown symbol comes back as `QuoteEntry::Error`, not an `Err`.
let quotes = client.market_data().quotes().list(["AAPL"]).send().await?;
let last_price = match quotes.get("AAPL") {
    Some(QuoteEntry::Equity(q)) => q.quote.as_ref().and_then(|inner| inner.last_price),
    _ => None,
};
let Some(last_price) = last_price else {
    println!("no quote for AAPL");
    return Ok(());
};

// 3. Place a limit buy just under the last trade; keep the order id
//    Schwab returns so the fill can be polled later.
let limit = last_price - dec!(0.50);
let order_id = client
    .orders(account_hash)
    .place(OrderRequest::buy_limit("AAPL", dec!(10), limit))
    .await?;
println!("placed order {order_id} at {limit}");

§Authentication

The Schwab APIs require a short-lived access token. You will need to obtain one using Schwab’s OAuth flow and either pass it to SchwabClient::new or make it available to the client via a TokenProvider. The TokenProider is the recommended mechanism for long-lived clients. The provider is consulted once per REST request and once per streamer LOGIN frame, so a rotated token is observed on the next call without rebuilding the client.

See the TokenProvider docs for examples of implementing a custom provider.

Note: This crate does not perform the OAuth authorization-code exchange See Schwab’s developer portal for details on their OAuth flow.

§Out of scope

  • The OAuth authorization-code flow. Callers obtain a bearer token out of band and hand it to SchwabClient::new, or implement TokenProvider for refresh-on-demand (see its doctest for a worked provider).
  • Retry and rate limiting. Each Error exposes Error::is_retryable and Error::retry_after so a caller can layer a policy (backon, etc.) on top. See the doctest on Error::is_retryable for a minimal backoff loop.
  • Idempotent order submission. Place / replace / cancel / preview exist, but the Schwab API exposes no client-controllable idempotency key; callers that need retry-safe submission must dedupe at their own layer.

§Security

schwab-sdk is built to reduce the risk of credential or PII leakage through this crate, not to be a security boundary for the application as a whole.

  • The secret newtypes (AuthToken, CustomerId, AccountNumber, AccountHash) redact in Debug and zeroise on Drop. The secrets module documents what these properties cover and what they do not.
  • The crate emits no log lines, writes no files, and does not embed secret values in Error variants. A bearer credential is materialised only at the Authorization header and the streamer LOGIN frame.
  • Transport defaults to HTTPS for REST and WSS for the streamer. Release builds reject http:// base-URL overrides and ws:// streamer URLs; debug builds permit them so local fixture servers work in tests.
  • Credential storage, the OAuth flow, retry policy, rate limiting, structured logging, and host-level hardening (disabling core dumps, encrypted swap) are caller responsibilities. See SECURITY.md in the repository for the vulnerability-reporting channel and the formal scope.

§Disclaimer

This crate is an independent 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.

The crate 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.

Re-exports§

pub use error::Error;
pub use error::ErrorBody;
pub use error::Result;
pub use secrets::AccountHash;
pub use secrets::AccountNumber;
pub use secrets::AuthToken;
pub use secrets::CustomerId;
pub use streamer::StreamerResponse;
pub use streamer::WebSocketError;
pub use chrono;
pub use http;
pub use rust_decimal;

Modules§

accounts
GET /accounts family
error
Crate-wide error type.
market_data
Schwab Market Data API.
orders
/orders and /accounts/{accountNumber}/orders*
secrets
Domain types for sensitive strings.
streamer
Schwab streamer WebSocket.
transactions
GET /accounts/{accountNumber}/transactions*
user_preferences
GET /userPreference - Schwab Trader API.

Macros§

decmacros
Construct a rust_decimal::Decimal from a numeric literal at compile time.

Structs§

SchwabClient
An HTTP client for the Charles Schwab Trader API.
StaticTokenProvider
TokenProvider that returns the same AuthToken for every call.

Constants§

DEFAULT_AUTH_TOKEN_EXPIRY
Lifetime of a freshly issued Schwab OAuth access token (AuthToken). Schwab issues access tokens valid for 30 minutes. A token may be revoked or invalidated earlier, so treat this as the issued upper bound rather than a guarantee.
DEFAULT_REFRESH_TOKEN_EXPIRY
Lifetime of a freshly issued Schwab OAuth refresh token. Schwab issues refresh tokens valid for 7 days; once it expires the full authorization flow must be re-run to obtain a new one.
MARKET_DATA_BASE_URL
Production base URL for Schwab’s Market Data API.
TRADER_BASE_URL
Production base URL for Schwab’s Trader API.

Traits§

TokenProvider
Source of the bearer token used on every Schwab REST request.