Building on @alpacahq/alpaca-trade-api
This is a map, not the territory. It gives you the mental model, the idioms
agents most often get wrong, and where to look — it deliberately omits the full
API surface. Whenever you need specifics (exact method names, parameters,
response fields), open the package README and grep the API reference: the
README is ~2750 lines and its second half is an auto-generated, per-method
reference. Do not guess method signatures from this file alone — confirm in the
README or the source.
Where the source of truth lives
Paths are relative to the package root — the repo root when developing here, or
node_modules/@alpacahq/alpaca-trade-api/ when installed (src/ ships in the
published tarball).
| You need… |
Read / search |
| The full per-method API (all REST methods, streams, ergonomic helpers) |
README.md → the ## API reference section (search the heading, then grep for the method name) |
| The narrative docs & idioms |
README.md → sections before ## API reference (Quick start, Placing orders, Pagination, Values & types, Normalized market-data shapes, Real-time streaming, …) |
| Runnable end-to-end examples |
examples/trading-bot.ts, examples/marketdata-backend.ts |
| The unified client / facade wiring |
src/client.ts |
| Order builders |
src/orders.ts |
| Normalized bar/trade/quote shapes + chart helpers |
src/marketDataShapes.ts |
| Programmatic capability maps |
src/capabilities.ts |
| Shared transport (retry/timeout/rate-limit/errors) |
src/core/runtime.ts |
| Streaming clients |
src/streaming/ |
When unsure where a method lives, prefer the programmatic lookups (below) over
guessing, then confirm against the README.
Mental model: a two-layer facade
The Alpaca client bundles every Trading and Market Data API behind one
constructor, reached via the .trading and .marketData namespaces.
- Generated (always present, uniform). Every generated REST method is
reachable raw at
alpaca.<group>.<resource>.<method>(...) — e.g.
alpaca.trading.assets.getV2Assets(), alpaca.marketData.stocks.stockBars(...).
Nothing is ever hidden.
- Ergonomic (additive, never replaces layer 1). Hand-written conveniences
sit on top: order builders, normalized market-data accessors, pagination,
workflow helpers. The raw method each one wraps is still available.
The rule: if there's no ergonomic helper for what you need, the raw
generated method is always there. You never have to choose between them.
Setup essentials
import { Alpaca } from "@alpacahq/alpaca-trade-api";
const alpaca = new Alpaca({
keyId: process.env.APCA_API_KEY_ID,
secret: process.env.APCA_API_SECRET_KEY,
paper: true, // DEFAULT. set false for LIVE trading — a deliberate flag.
});
- Credentials resolve from env vars when omitted:
APCA_API_KEY_ID,
APCA_API_SECRET_KEY, APCA_API_OAUTH_TOKEN. A non-empty explicit
accessToken selects OAuth; otherwise any non-empty explicit key field
selects key authentication ahead of an environment token. Empty strings are
absent. With no explicit scheme, environment OAuth takes precedence over
environment keys.
- Never pass
apiKey as a plain string — Alpaca needs two distinct headers
and will reject a single value (the SDK throws a guided error). Use OAuth via
accessToken, or auth.apiKeyAuth({ keyId, secret }) for lazy credentials.
- OAuth-only clients cannot open WebSocket streams (streaming needs
key/secret).
Idioms agents get wrong (read these before writing code)
- Money & quantities are numeric
strings (wire-truthful, no float loss).
Convert for display/compute with the values helpers (values.toNumber,
values.toNumberOr, values.formatMoney); for exact math keep the string and
feed a decimal library. Never parseFloat blindly for arithmetic.
- Timeframes are branded. Don't hand-write
"1minute" (the API rejects it).
Build with TimeFrame.Day / timeFrame(15, TimeFrameUnit.Minute) → the
branded TimeFrameString the facade bar methods require.
- Orders: prefer the ergonomic builders on
alpaca.trading.orders
(market, limit, stop, stopLimit, trailingStop, bracket, oco,
oto) — they drop the postOrder({ postOrderRequest }) wrapper and enforce
required fields per kind at compile time. For uncovered shapes (e.g. multi-leg
mleg) use orders.submit(input) or the raw postOrder.
- Order safety: agent-generated trading code must put a stable, unique
clientOrderId in every order request and persist it with the strategy's
audit record. Never add a custom idempotency header and never retry an
order-placement POST. If a FetchError makes placement ambiguous, call
orders.getOrderByClientOrderId({ clientOrderId }) before any further
submission. Duplicate client IDs are rejected; they do not replay the prior
response. A lookup miss is not proof that placement failed, and visibility is
not guaranteed to become eventual.
- Pagination is built in.
iterate* lazily yields across all pages;
collect* / collect*BySymbol eagerly return them — never thread page tokens
by hand. For big back-fills pass SymbolCollectOptions
(maxPerSymbol, concurrency, chunkSize) to bound memory / parallelize.
Every helper stops before refetching any previously visited token/cursor,
including longer cycles, after preserving valid pages already fetched.
- REST and streaming share one shape. The normalized accessors
(
getStockBars, getCryptoTrades, …; single-symbol *For(symbol) variants;
chart-ready get*Candles) return the SAME Bar/Trade/Quote type the
streams emit — backfill over REST then append live updates without remapping.
The raw generated map responses (e.g. marketData.stocks.stockBars) keep
Alpaca's compact wire keys ({ o, h, l, c, v, … }) and may carry ISO-string
timestamps; prefer the normalized accessors or marketDataShapes.toBar etc.
A single-symbol *For reads only the exact requested key; absent data is []
or empty Candles, never another symbol's value.
- Errors are typed. Non-2xx rejects with
ApiError and status-specific
subclasses (AuthError 401, PermissionError 403, NotFoundError 404,
ValidationError 400/422, RateLimitError 429). Branch on the subclass, not
magic numbers. Always log err.requestId (Alpaca's X-Request-ID) — it can't
be looked up later. Network/abort failures reject with FetchError.
- Resilience is opt-in but conservative.
timeoutMs, retry
(maxRetries > 0 to enable; non-idempotent POSTs are never auto-retried),
rateLimit (the Alpaca client enables a safe ~200/min default; raw Api
classes do not), and userAgent are all top-level client options. The
default User-Agent is
APCA-NODE/<sdk-version> <Runtime>/<runtime-version>; override it with
userAgent, or set userAgent: "" to disable it. The retry
config also takes onRetry/onGiveUp observability hooks (each fired with a
RetryEvent; thrown exceptions are swallowed so they can't break a request).
timeoutMs is fresh per attempt and covers rate-limit wait, pre/fetch/post/
error middleware, and success/error body reads. Backoff is outside that
attempt budget; caller abort spans the operation/backoff. Every cancellation
phase throws FetchError with AbortError/TimeoutError cause.
Requests default to redirect: "error" (3xx fails fast) so the APCA-API-*
secret headers can't follow an off-host redirect; set redirect: "follow" to
opt out.
- Response metadata. Methods return just the body; to also read status,
headers, or rate-limit metadata of a successful call, wrap the generated
*Raw sibling with withResponse(...) → { data, status, headers, rateLimit }
(AlpacaApiResponse<T>).
- REST-only builds: import from
@alpacahq/alpaca-trade-api/rest to keep
ws/@msgpack/msgpack out of the module graph. Stream factories and
submitAndWait throw from this entrypoint — import the root package for
streams. On edge/browser runtimes (Cloudflare Workers/workerd, Vercel Edge,
Deno, browsers) the root import auto-resolves to this REST-only build via the
package exports conditions, so streaming is unavailable there (ws can't
run on edge) but REST works. The REST runtime graph and declarations are free
of Node, ws, and msgpack requirements, including strict Node projects
without DOM libs.
- ESM & CJS dual package: don't load the SDK through both
import and
require in one process if you rely on instanceof against its classes
(e.g. ApiError) — you may compare against two copies.
Streaming (quick shape)
WebSocket clients are typed EventEmitters: register listeners, then
connect(). They auto-authenticate, reconnect with backoff, and re-subscribe.
const stocks = alpaca.marketData.stockStream({ feed: "iex" }); // iex | sip | delayed_sip
stocks.onBar((bar) => {/* canonical Bar */});
stocks.onConnect(() => stocks.subscribeForBars(["AAPL", "MSFT"]));
stocks.connect();
cryptoStream(), optionStream(), newsStream(), and the trading
alpaca.trading.stream() (order/account updates) share the same surface. The
submitAndWait workflow helper waits for the server's listening
acknowledgement, then issues one placement per invocation and resolves on a
terminal update. It preserves a supplied client ID or creates one once, never
re-places on reconnect, and uses one deadline for connect, authentication,
subscription, REST placement, and waiting. Only this helper performs one
client-ID lookup after an ambiguous FetchError; generic builders do not
reconcile automatically. It does not guarantee exactly-once execution or
eventual lookup visibility.
Every stream also exposes (parity with the Java client):
- Awaitable auth:
await stream.whenAuthenticated() →
StreamAuthResult { status, authenticated, code?, message } (never rejects);
waitForAuthentication(timeoutMs?) → boolean. status is a
STREAM_AUTH_STATUS (authenticated/server_rejected/closed/timeout).
- Reconnect lifecycle:
onReconnecting((attempt) => …) (1-based) and
onReconnected(() => …) after auth and re-subscription dispatch, distinct
from the first onConnect; it is not server subscription acknowledgement.
- Overrides: a per-stream
url (proxy/gateway routing on any stream,
market-data included) and a callbackExecutor to offload + isolate listeners
(a throwing listener is logged, never breaks the stream).
- Gotchas: crypto & news streams are production-only —
sandbox: true
throws (the client sandbox flag isn't applied to them); pass url to
override. Subscribing with blank/non-string symbols throws at the call site.
Socket generations isolate stale callbacks/timers, pings start only while
open, manual disconnect emits once, and malformed/decode/mapper or trading
action: "error" failures surface through onError / CLIENT_ERROR.
Discovering anything programmatically
import { findCapabilities, findErgonomic } from "@alpacahq/alpaca-trade-api";
findCapabilities("getAccount"); // generated: which Api / accessor hosts it
findErgonomic("market"); // ergonomic: is there a helper, and where
capabilities / ergonomicCapabilities / streamingCapabilities are the full
maps. Use these (or grep the README ## API reference) to answer "where does X
live?" instead of guessing.
Testing integrations
@alpacahq/alpaca-trade-api/testing is a network-free harness: createMockAlpaca([...])
returns a ready client backed by canned responses matched by method + path; use
it so unit tests never hit Alpaca.
When you're unsure
Don't stop at this file. Open README.md and read the relevant ## section, or
grep the ## API reference for the exact method, or read the src/ module in
the table above. This skill points the way; the README and source are
authoritative.
1---2name: alpaca-trade-api-sdk3description: Integrate and build on the @alpacahq/alpaca-trade-api TypeScript SDK for the Alpaca Trading and Market Data APIs (the unified Alpaca client, ergonomic order builders, normalized market-data shapes, pagination, typed errors, resilience, and real-time streaming). Use when writing or reviewing code that imports @alpacahq/alpaca-trade-api, places orders, fetches bars/trades/quotes, opens market-data or trading WebSocket streams, or builds a trading bot, backtester, or market-data backend on top of this SDK.4---56# Building on @alpacahq/alpaca-trade-api78This is a **map, not the territory.** It gives you the mental model, the idioms9agents most often get wrong, and where to look — it deliberately omits the full10API surface. Whenever you need specifics (exact method names, parameters,11response fields), **open the package README and grep the API reference**: the12README is ~2750 lines and its second half is an auto-generated, per-method13reference. Do not guess method signatures from this file alone — confirm in the14README or the source.1516## Where the source of truth lives1718Paths are relative to the package root — the repo root when developing here, or19`node_modules/@alpacahq/alpaca-trade-api/` when installed (`src/` ships in the20published tarball).2122| You need… | Read / search |23| --- | --- |24| The full per-method API (all REST methods, streams, ergonomic helpers) | `README.md` → the `## API reference` section (search the heading, then grep for the method name) |25| The narrative docs & idioms | `README.md` → sections before `## API reference` (`Quick start`, `Placing orders`, `Pagination`, `Values & types`, `Normalized market-data shapes`, `Real-time streaming`, …) |26| Runnable end-to-end examples | `examples/trading-bot.ts`, `examples/marketdata-backend.ts` |27| The unified client / facade wiring | `src/client.ts` |28| Order builders | `src/orders.ts` |29| Normalized bar/trade/quote shapes + chart helpers | `src/marketDataShapes.ts` |30| Programmatic capability maps | `src/capabilities.ts` |31| Shared transport (retry/timeout/rate-limit/errors) | `src/core/runtime.ts` |32| Streaming clients | `src/streaming/` |3334When unsure where a method lives, prefer the programmatic lookups (below) over35guessing, then confirm against the README.3637## Mental model: a two-layer facade3839The `Alpaca` client bundles every Trading and Market Data API behind one40constructor, reached via the `.trading` and `.marketData` namespaces.41421. **Generated (always present, uniform).** Every generated REST method is43 reachable raw at `alpaca.<group>.<resource>.<method>(...)` — e.g.44 `alpaca.trading.assets.getV2Assets()`, `alpaca.marketData.stocks.stockBars(...)`.45 Nothing is ever hidden.462. **Ergonomic (additive, never replaces layer 1).** Hand-written conveniences47 sit on top: order builders, normalized market-data accessors, pagination,48 workflow helpers. The raw method each one wraps is still available.4950**The rule:** if there's no ergonomic helper for what you need, the raw51generated method is always there. You never have to choose between them.5253## Setup essentials5455```ts56import { Alpaca } from "@alpacahq/alpaca-trade-api";5758const alpaca = new Alpaca({59 keyId: process.env.APCA_API_KEY_ID,60 secret: process.env.APCA_API_SECRET_KEY,61 paper: true, // DEFAULT. set false for LIVE trading — a deliberate flag.62});63```6465- Credentials resolve from env vars when omitted: `APCA_API_KEY_ID`,66 `APCA_API_SECRET_KEY`, `APCA_API_OAUTH_TOKEN`. A non-empty explicit67 `accessToken` selects OAuth; otherwise any non-empty explicit key field68 selects key authentication ahead of an environment token. Empty strings are69 absent. With no explicit scheme, environment OAuth takes precedence over70 environment keys.71- **Never** pass `apiKey` as a plain string — Alpaca needs two distinct headers72 and will reject a single value (the SDK throws a guided error). Use OAuth via73 `accessToken`, or `auth.apiKeyAuth({ keyId, secret })` for lazy credentials.74- OAuth-only clients **cannot** open WebSocket streams (streaming needs75 key/secret).7677## Idioms agents get wrong (read these before writing code)7879- **Money & quantities are numeric `string`s** (wire-truthful, no float loss).80 Convert for display/compute with the `values` helpers (`values.toNumber`,81 `values.toNumberOr`, `values.formatMoney`); for exact math keep the string and82 feed a decimal library. Never `parseFloat` blindly for arithmetic.83- **Timeframes are branded.** Don't hand-write `"1minute"` (the API rejects it).84 Build with `TimeFrame.Day` / `timeFrame(15, TimeFrameUnit.Minute)` → the85 branded `TimeFrameString` the facade bar methods require.86- **Orders:** prefer the ergonomic builders on `alpaca.trading.orders`87 (`market`, `limit`, `stop`, `stopLimit`, `trailingStop`, `bracket`, `oco`,88 `oto`) — they drop the `postOrder({ postOrderRequest })` wrapper and enforce89 required fields per kind at compile time. For uncovered shapes (e.g. multi-leg90 `mleg`) use `orders.submit(input)` or the raw `postOrder`.91- **Order safety:** agent-generated trading code must put a stable, unique92 `clientOrderId` in every order request and persist it with the strategy's93 audit record. Never add a custom idempotency header and never retry an94 order-placement `POST`. If a `FetchError` makes placement ambiguous, call95 `orders.getOrderByClientOrderId({ clientOrderId })` before any further96 submission. Duplicate client IDs are rejected; they do not replay the prior97 response. A lookup miss is not proof that placement failed, and visibility is98 not guaranteed to become eventual.99- **Pagination is built in.** `iterate*` lazily yields across all pages;100 `collect*` / `collect*BySymbol` eagerly return them — never thread page tokens101 by hand. For big back-fills pass `SymbolCollectOptions`102 (`maxPerSymbol`, `concurrency`, `chunkSize`) to bound memory / parallelize.103 Every helper stops before refetching any previously visited token/cursor,104 including longer cycles, after preserving valid pages already fetched.105- **REST and streaming share one shape.** The normalized accessors106 (`getStockBars`, `getCryptoTrades`, …; single-symbol `*For(symbol)` variants;107 chart-ready `get*Candles`) return the SAME `Bar`/`Trade`/`Quote` type the108 streams emit — backfill over REST then append live updates without remapping.109 The **raw** generated map responses (e.g. `marketData.stocks.stockBars`) keep110 Alpaca's compact wire keys (`{ o, h, l, c, v, … }`) and may carry ISO-string111 timestamps; prefer the normalized accessors or `marketDataShapes.toBar` etc.112 A single-symbol `*For` reads only the exact requested key; absent data is `[]`113 or empty `Candles`, never another symbol's value.114- **Errors are typed.** Non-2xx rejects with `ApiError` and status-specific115 subclasses (`AuthError` 401, `PermissionError` 403, `NotFoundError` 404,116 `ValidationError` 400/422, `RateLimitError` 429). Branch on the subclass, not117 magic numbers. Always log `err.requestId` (Alpaca's `X-Request-ID`) — it can't118 be looked up later. Network/abort failures reject with `FetchError`.119- **Resilience is opt-in but conservative.** `timeoutMs`, `retry`120 (`maxRetries > 0` to enable; non-idempotent POSTs are never auto-retried),121 `rateLimit` (the `Alpaca` client enables a safe ~200/min default; raw `Api`122 classes do not), and `userAgent` are all top-level client options. The123 default User-Agent is124 `APCA-NODE/<sdk-version> <Runtime>/<runtime-version>`; override it with125 `userAgent`, or set `userAgent: ""` to disable it. The `retry`126 config also takes `onRetry`/`onGiveUp` observability hooks (each fired with a127 `RetryEvent`; thrown exceptions are swallowed so they can't break a request).128 `timeoutMs` is fresh per attempt and covers rate-limit wait, pre/fetch/post/129 error middleware, and success/error body reads. Backoff is outside that130 attempt budget; caller abort spans the operation/backoff. Every cancellation131 phase throws `FetchError` with `AbortError`/`TimeoutError` cause.132 Requests default to `redirect: "error"` (3xx fails fast) so the `APCA-API-*`133 secret headers can't follow an off-host redirect; set `redirect: "follow"` to134 opt out.135- **Response metadata.** Methods return just the body; to also read status,136 headers, or rate-limit metadata of a successful call, wrap the generated137 `*Raw` sibling with `withResponse(...)` → `{ data, status, headers, rateLimit }`138 (`AlpacaApiResponse<T>`).139- **REST-only builds:** import from `@alpacahq/alpaca-trade-api/rest` to keep140 `ws`/`@msgpack/msgpack` out of the module graph. Stream factories and141 `submitAndWait` throw from this entrypoint — import the root package for142 streams. On edge/browser runtimes (Cloudflare Workers/`workerd`, Vercel Edge,143 Deno, browsers) the root import auto-resolves to this REST-only build via the144 package `exports` conditions, so streaming is unavailable there (`ws` can't145 run on edge) but REST works. The REST runtime graph and declarations are free146 of Node, `ws`, and msgpack requirements, including strict Node projects147 without DOM libs.148- **ESM & CJS dual package:** don't load the SDK through both `import` and149 `require` in one process if you rely on `instanceof` against its classes150 (e.g. `ApiError`) — you may compare against two copies.151152## Streaming (quick shape)153154WebSocket clients are typed `EventEmitter`s: register listeners, then155`connect()`. They auto-authenticate, reconnect with backoff, and re-subscribe.156157```ts158const stocks = alpaca.marketData.stockStream({ feed: "iex" }); // iex | sip | delayed_sip159stocks.onBar((bar) => {/* canonical Bar */});160stocks.onConnect(() => stocks.subscribeForBars(["AAPL", "MSFT"]));161stocks.connect();162```163164`cryptoStream()`, `optionStream()`, `newsStream()`, and the trading165`alpaca.trading.stream()` (order/account updates) share the same surface. The166`submitAndWait` workflow helper waits for the server's `listening`167acknowledgement, then issues one placement per invocation and resolves on a168terminal update. It preserves a supplied client ID or creates one once, never169re-places on reconnect, and uses one deadline for connect, authentication,170subscription, REST placement, and waiting. Only this helper performs one171client-ID lookup after an ambiguous `FetchError`; generic builders do not172reconcile automatically. It does not guarantee exactly-once execution or173eventual lookup visibility.174175Every stream also exposes (parity with the Java client):176177- **Awaitable auth:** `await stream.whenAuthenticated()` →178 `StreamAuthResult { status, authenticated, code?, message }` (never rejects);179 `waitForAuthentication(timeoutMs?)` → `boolean`. `status` is a180 `STREAM_AUTH_STATUS` (`authenticated`/`server_rejected`/`closed`/`timeout`).181- **Reconnect lifecycle:** `onReconnecting((attempt) => …)` (1-based) and182 `onReconnected(() => …)` after auth and re-subscription dispatch, distinct183 from the first `onConnect`; it is not server subscription acknowledgement.184- **Overrides:** a per-stream `url` (proxy/gateway routing on any stream,185 market-data included) and a `callbackExecutor` to offload + isolate listeners186 (a throwing listener is logged, never breaks the stream).187- **Gotchas:** crypto & news streams are **production-only** — `sandbox: true`188 throws (the client `sandbox` flag isn't applied to them); pass `url` to189 override. Subscribing with blank/non-string symbols throws at the call site.190 Socket generations isolate stale callbacks/timers, pings start only while191 open, manual disconnect emits once, and malformed/decode/mapper or trading192 `action: "error"` failures surface through `onError` / `CLIENT_ERROR`.193194## Discovering anything programmatically195196```ts197import { findCapabilities, findErgonomic } from "@alpacahq/alpaca-trade-api";198199findCapabilities("getAccount"); // generated: which Api / accessor hosts it200findErgonomic("market"); // ergonomic: is there a helper, and where201```202203`capabilities` / `ergonomicCapabilities` / `streamingCapabilities` are the full204maps. Use these (or grep the README `## API reference`) to answer "where does X205live?" instead of guessing.206207## Testing integrations208209`@alpacahq/alpaca-trade-api/testing` is a network-free harness: `createMockAlpaca([...])`210returns a ready client backed by canned responses matched by method + path; use211it so unit tests never hit Alpaca.212213## When you're unsure214215Don't stop at this file. Open `README.md` and read the relevant `##` section, or216grep the `## API reference` for the exact method, or read the `src/` module in217the table above. This skill points the way; the README and source are218authoritative.