alpaca-data 0.10.3

High-performance Rust client for Alpaca Market Data API
Documentation

alpaca-data

alpaca-data is an async Rust client for the Alpaca Market Data HTTP API.

Quick links:

The crate is built around two constraints:

  • The official Alpaca Market Data HTTP API is the only semantic source of truth.
  • Public Rust naming is idiomatic Rust, while request and response field words use the official API terms.

Status

  • Current branch baseline: v0.10.3
  • Implemented resource families: stocks, options, crypto, news, corporate_actions
  • This repository does not cover Trading API, Broker API, WebSocket, or SSE
  • This crate is async-only
  • crates.io package: alpaca-data
  • Public docs include a GitHub Pages site, generated API reference pages, rustdoc links, API coverage docs, and a tag-triggered release workflow

Release automation is intentionally tag-triggered only and follows GitHub-hosted stable. After the initial manual crates.io bootstrap release, the github-pages workflow runs the standard Rust validation steps, publishes the crate to crates.io through Trusted Publishing, and deploys the documentation build only on pushed release tags such as vX.Y.Z. The manifest intentionally omits rust-version until an audited MSRV policy exists.

Design Contract

Official HTTP API first

  • Endpoint semantics follow the official Alpaca Market Data API.
  • Request fields and response fields use the original official words.
  • Rust-specific adaptation is kept minimal and only used where the language requires it, such as r#type.

Idiomatic Rust public API

  • Crate name: alpaca_data
  • Root client: alpaca_data::Client
  • Resource accessors: stocks(), options(), crypto(), news(), corporate_actions()
  • Modules are lowercase, types are PascalCase, methods and fields are snake_case

Two API layers

The crate exposes two layers:

  • Mirror layer: direct Rust wrappers for the official HTTP endpoints
  • Convenience layer: *_all and *_stream helpers on top of official paginated endpoints

The convenience layer never changes the official payload words. It only automates pagination.

Request guardrails stay official-only

  • The crate fails fast with Error::InvalidRequest only for clearly documented Alpaca request rules.
  • Examples include empty required symbol lists, blank required single-path identifiers such as symbol or underlying_symbol, documented limit bounds, the options symbol-list cap, and the corporate-actions ids exclusivity rule.
  • The crate does not silently auto-chunk mirror requests to work around documented hard limits.

Coverage Summary

Stocks

  • Historical batch: auctions, bars, quotes, trades
  • Historical single-symbol: auctions_single, bars_single, quotes_single, trades_single
  • Convenience: auctions_all, auctions_stream, bars_all, bars_stream, quotes_all, quotes_stream, trades_all, trades_stream
  • Single-symbol convenience: auctions_single_all, auctions_single_stream, bars_single_all, bars_single_stream, quotes_single_all, quotes_single_stream, trades_single_all, trades_single_stream
  • Latest: latest_bars, latest_bar, latest_quotes, latest_quote, latest_trades, latest_trade
  • Snapshots and metadata: snapshots, snapshot, condition_codes, exchange_codes

Options

  • Historical: bars, trades
  • Convenience: bars_all, bars_stream, trades_all, trades_stream
  • Latest: latest_quotes, latest_trades
  • Snapshot family: snapshots, snapshots_all, snapshots_stream, chain, chain_all, chain_stream
  • Metadata: condition_codes, exchange_codes

Crypto

  • Historical: bars, quotes, trades
  • Convenience: bars_all, bars_stream, quotes_all, quotes_stream, trades_all, trades_stream
  • Latest: latest_bars, latest_quotes, latest_trades, latest_orderbooks
  • Snapshots: snapshots

News

  • list, list_all, list_stream

Corporate Actions

  • list, list_all, list_stream

Quick Start

Install from crates.io:

[dependencies]
alpaca-data = "0.10.3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Build a client:

use alpaca_data::Client;

let client = Client::new();
let client = Client::builder().build()?;
# Ok::<(), alpaca_data::Error>(())

Tune transport retries when a service integration needs stronger transient-failure handling:

use std::time::Duration;
use alpaca_data::Client;

let client = Client::builder()
    .timeout(Duration::from_secs(5))
    .max_retries(2)
    .retry_on_429(true)
    .respect_retry_after(true)
    .base_backoff(Duration::from_millis(100))
    .max_backoff(Duration::from_millis(500))
    .max_in_flight(32)
    .build()?;
# let _ = client;
# Ok::<(), alpaca_data::Error>(())

The default retry behavior stays conservative: retrying 429 responses and honoring Retry-After remain opt-in.

When you set total_retry_budget(...), the retry loop is bounded by the remaining elapsed-time budget, and each scheduled retry wait is capped by that remaining budget, including waits derived from Retry-After and waits with jitter enabled.

Inject a custom reqwest::Client when you need to own the underlying network stack:

use std::time::Duration;
use alpaca_data::Client;

let reqwest_client = reqwest::Client::builder()
    .timeout(Duration::from_secs(5))
    .build()?;

let client = Client::builder()
    .reqwest_client(reqwest_client)
    .max_retries(2)
    .retry_on_429(true)
    .respect_retry_after(true)
    .base_backoff(Duration::from_millis(100))
    .max_backoff(Duration::from_millis(500))
    .max_in_flight(32)
    .build()?;
# let _ = client;
# Ok::<(), Box<dyn std::error::Error>>(())

When reqwest_client(...) is used, reqwest-level settings such as timeout(...) must be configured on the injected client instead of on ClientBuilder.

Optionally load paired credentials from process environment when a service prefers env-driven wiring:

use alpaca_data::Client;

let client = Client::builder()
    .credentials_from_env()?
    .build()?;
# let _ = client;
# Ok::<(), alpaca_data::Error>(())

Observe successful transport completions without changing endpoint return types:

use std::sync::Arc;
use alpaca_data::{Client, ObservedResponseMeta, TransportObserver};

struct LoggingObserver;

impl TransportObserver for LoggingObserver {
    fn on_response(&self, meta: &ObservedResponseMeta) {
        println!(
            "endpoint={}, status={}, request_id={:?}",
            meta.endpoint_name, meta.status, meta.request_id
        );
    }
}

let client = Client::builder()
    .observer(Arc::new(LoggingObserver))
    .build()?;
# let _ = client;
# Ok::<(), alpaca_data::Error>(())

Observers only receive successful-response metadata such as endpoint name, URL, status, request ID, retry attempt count, and elapsed time.

Fetch stock latest bars:

use alpaca_data::{Client, stocks};

#[tokio::main]
async fn main() -> Result<(), alpaca_data::Error> {
    let client = Client::builder()
        .api_key(std::env::var("APCA_API_KEY_ID").expect("APCA_API_KEY_ID is required"))
        .secret_key(std::env::var("APCA_API_SECRET_KEY").expect("APCA_API_SECRET_KEY is required"))
        .build()?;

    let response = client
        .stocks()
        .latest_bars(stocks::LatestBarsRequest {
            symbols: vec!["AAPL".into()],
            feed: None,
            currency: None,
        })
        .await?;

    println!("{response:?}");
    Ok(())
}

Collect all pages from a paginated endpoint:

use alpaca_data::{Client, news};

#[tokio::main]
async fn main() -> Result<(), alpaca_data::Error> {
    let client = Client::builder()
        .api_key(std::env::var("APCA_API_KEY_ID").expect("APCA_API_KEY_ID is required"))
        .secret_key(std::env::var("APCA_API_SECRET_KEY").expect("APCA_API_SECRET_KEY is required"))
        .build()?;

    let response = client
        .news()
        .list_all(news::ListRequest {
            start: Some("2026-04-01T00:00:00Z".into()),
            end: Some("2026-04-04T00:00:00Z".into()),
            sort: Some(news::Sort::Desc),
            symbols: Some(vec!["AAPL".into()]),
            limit: Some(50),
            include_content: Some(false),
            exclude_contentless: Some(true),
            page_token: None,
        })
        .await?;

    println!("{}", response.news.len());
    Ok(())
}

Authentication

Authentication behavior follows the implemented endpoint rules:

  • stocks, options, news, and corporate_actions require credentials
  • The currently implemented crypto HTTP endpoints can run without credentials

The primary application path is still explicit credentials on the builder:

use alpaca_data::Client;

let client = Client::builder()
    .api_key(std::env::var("APCA_API_KEY_ID").expect("APCA_API_KEY_ID is required"))
    .secret_key(std::env::var("APCA_API_SECRET_KEY").expect("APCA_API_SECRET_KEY is required"))
    .build()?;
# let _ = client;
# Ok::<(), alpaca_data::Error>(())

credentials_from_env() and credentials_from_env_names(...) are optional ergonomics for integrations that already manage credentials through environment variables. They only load paired values; a partial pair returns Error::InvalidConfiguration.

Live tests in this repository use:

  • APCA_API_KEY_ID
  • APCA_API_SECRET_KEY
  • ALPACA_LIVE_TESTS=1

If you keep local secrets in a .env file under different names such as ALPACA_DATA_API_KEY and ALPACA_DATA_SECRET_KEY, export them into the APCA_* names before running live tests or examples.

See docs/authentication.md for the current auth contract.

Documentation Map

API Audit Script

The repository includes one local entry point for official Market Data API audit work.

Requirements:

  • bash
  • curl
  • jq

Run the read-only parity audit against the local coverage manifest and source tree:

./scripts/api-sync-audit

The script accepts no command-line arguments. It prints the complete audit report directly to the terminal and exits non-zero when blocking drift is detected. The report covers mirror-path coverage, parameter signatures, response-field signatures, enum gaps, and convenience helpers that require re-validation after mirror changes.

Testing

Default checks:

cargo test

Enable live tests:

ALPACA_LIVE_TESTS=1 cargo test --test live_crypto_latest_quotes_smoke -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_stocks_batch_historical -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_stocks_single_historical -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_stocks_latest_snapshot -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_stocks_metadata -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_options_historical -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_options_latest_metadata -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_options_snapshots_chain -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_news -- --nocapture
ALPACA_LIVE_TESTS=1 cargo test --test live_corporate_actions -- --nocapture

Normal success-path coverage must use the real Alpaca API whenever credentials can cover the scenario. Mocks are reserved for fault injection paths such as malformed JSON, repeated pagination tokens, timeouts, and HTTP failures.

Benchmarks

Local micro-benchmark baselines currently live in:

  • benches/shared_core.rs
  • benches/stocks.rs
  • benches/options.rs
  • benches/crypto.rs
  • benches/news_corporate_actions.rs

Compile benchmark targets without running a full sample:

cargo bench --no-run