Skip to main content
Alpaca

Alpaca's Trading API → the Naive trading primitive

Alpaca gives an AI agent a brokerage API: read an account, pull quotes, place/cancel orders, and close positions across stocks, options, and crypto — one endpoint where the order symbol decides the market (AAPL, BTC/USD, AAPL241213C00250000). It does that job well, and the API is mature. But the direct Trading API is also a separate vendor account:
  • The brokerage lives behind an APCA-API-KEY-ID + APCA-API-SECRET-KEY pair, its own dashboard, and its own funding — disconnected from wherever the agent’s cards, email, secrets, and KYC live.
  • One key pair = one account. To trade on behalf of many end-users you must adopt Alpaca’s OAuth2 flow or the Broker API yourself — registering an app, exchanging codes for tokens, storing and encrypting each user’s token, and isolating per user.
  • “Who let this agent place a $5,000 order, and what else can it touch?” is answered in Alpaca for trading, and in unrelated systems for everything else. There is no execution-time approval gate on money-moving actions — a leaked key trades freely until you notice.
Naive’s trading primitive gives the agent the same capability — it literally proxies the Alpaca Trading API v2 on the user’s behalf — but every order is rooted in one governed identity:
  • The tenant user that links the brokerage is the same user that owns its cards, its email inboxes, its vault secrets, and its KYC.
  • Naive owns the platform OAuth app and performs the token exchange itself, so you never touch OAuth plumbing, token storage, or per-user isolationconnect() returns an authorize URL and Naive stores the bearer token encrypted at rest.
  • Whether an agent may place, cancel, or close — and whether each money-moving action freezes for human approval — is decided by that user’s Account Kit and per-agent assignment at execution time, not by an API-key scope you manage separately.
This guide maps Alpaca’s Trading API to Naive’s, shows the smallest working swap, and is explicit about what does not map yet.
Alpaca is a trademark of Alpaca Securities LLC, used here for identification only. Naive is not a broker, broker-dealer, or investment adviser and does not exercise trading discretion — the connected brokerage (Alpaca) is the broker, and the user directs and approves the trades. No endorsement or affiliation is implied beyond the documented Trading API proxy. See the trading primitive for the full disclosure.
Tested against: the Alpaca Node helper library @alpacahq/alpaca-trade-api v3.1.3 against the Alpaca Trading API v2 (paper base https://paper-api.alpaca.markets, live base https://api.alpaca.markets, data base https://data.alpaca.markets, docs snapshot June 2026), and the Naive Node SDK @usenaive-sdk/server against the Naive API (base https://api.usenaive.ai/v1, docs snapshot June 2026).Version notes:
  • Order schema is shared. Naive forwards the same Trading-API order fields Alpaca documents — symbol, qty/notional, side, type, time_in_force, limit_price, stop_price, order_class, extended_hours, client_order_id. The before/after below is close to a rename.
  • Auth model differs. Alpaca’s direct Trading API authenticates with a static key pair for one account. Naive uses Alpaca’s OAuth2 Connect flow per user: trading.connect({ env }) returns an authorize_url; Naive exchanges the code (scope account:write trading data) and stores the token. A user may link paper and/or live (env: "paper" | "live").
  • Money-moving actions are approval-gated by default on Naive (createOrder, cancelOrder, closePosition) — Alpaca’s Trading API has no equivalent. See gain #2.
  • No equivalent yet for Alpaca’s Broker API (opening/funding brokerage accounts), real-time streaming, historical market data, order replace, watchlists, or portfolio history — see what doesn’t map yet.

Concept map

Before / after: the core path

The path that matters for almost every trading agent is read the account, then place an order. Here it is on both platforms.
The order shape lines up almost exactly. The real differences to plan for:
  • One-time connect, not a static key. Alpaca’s direct API authenticates with a key pair for a single account. Naive links each user’s brokerage once via OAuth and proxies thereafter — you never store or encrypt the token.
  • Money-moving actions can defer. createOrder, cancelOrder, and closePosition are in Naive’s default approval set. Handle isPendingApproval(res) and approvals.wait; reads (account, positions, orders, quote) never defer.
  • env rides on the call. Where Alpaca picks paper/live at client construction, Naive takes env per call (a user can link both).
  • The id is your identity, not a separate account. In Alpaca the key pair scopes Alpaca. In Naive the same forUser(id) handle also owns the agent’s cards, email, vault, and KYC.

Stocks, options, and crypto

The same createOrder endpoint trades every asset class on both platforms — the symbol selects the market. The field names are identical.
  • Crypto (BTC/USD) trades 24/7 with time_in_force gtc/ioc; the brokerage may enforce minimum order sizes for crypto — see trading and Alpaca’s docs.
  • Options use OCC symbols (AAPL241213C00250000); crypto trading must be enabled on the user’s brokerage account (trading.account()crypto_status) — Naive surfaces it but does not toggle it.

Minimal viable migration

The smallest swap that keeps a working agent running is connect + read + place/cancel/close.
1

Install the SDK and set your key

Set NAIVE_API_KEY (a server-side key from the dashboard).
2

Link the brokerage once (replaces your key pair / OAuth code)

Replace new Alpaca({ keyId, secretKey, paper }) with naive.forUser(id).trading.connect({ env }). Open the returned authorize_url; Naive exchanges the code and stores the token. Drop any hand-built OAuth callback, token table, and encryption — Naive owns all of it. Confirm with client.trading.connections() (status active).
3

Swap reads

Map getAccounttrading.account, getPositionstrading.positions, getOrderstrading.orders, getLatestQuotes/getLatestCryptoQuotestrading.quote. These never require approval.
4

Swap money-moving actions

Map createOrdertrading.createOrder (same fields), cancelOrdertrading.cancelOrder, closePositiontrading.closePosition. Wrap each in isPendingApproval(res) + client.approvals.wait(res.approval_id) so a deferred action resumes after a human approves.
5

Keep idempotency

If you set client_order_id for dedupe, pass it on createOrder directly — or, on raw REST, send an Idempotency-Key header when client_order_id is omitted. Naive uses client_order_id when provided; otherwise it forwards the Idempotency-Key as client_order_id, so a retried logical order can deduplicate at the broker.
6

Ship it

At this point you are off Alpaca’s direct API for the core trade path. Everything below is upside, not a requirement.

Consolidate further once you’re on Naive

This is where the migration pays for itself. In Alpaca, the key pair isolates a brokerage account and nothing else, and money-moving actions run the instant a key is used. On Naive, the unit of isolation is a tenant user, and every order is governed by that identity’s policy at execution time.

Gain #1 — one identity across primitives

  • With Alpaca, the agent’s brokerage is an island behind a key pair. With Naive, naive.forUser(acme.id) is a single handle to trading and cards and email and vault and KYC.
  • Tear the customer down from one place; sibling tenants should not be able to read each other’s orders, positions, cards, or secrets. The brokerage token is stored encrypted at rest and is never handed to the agent.

Gain #2 — execution-time permission enforcement

  • Whether an agent may trade at all is the trading primitive on the user’s Account Kit, and whether each order freezes for human review is requiresApproval policy — not a check you hand-write and not an Alpaca key scope you manage separately.
  • The agent’s code is identical for every tier — client.trading.createOrder({ ... }). Whether the order runs is decided at execution time:
    • A user whose Account Kit does not enable trading is refused with forbidden, with no code change on your side.
    • With trading.requiresApproval: true, createOrder / cancelOrder / closePosition return 202 { status: "pending_approval" } and the broker call replays only after a human approves. Reads (account, positions, quote) are never gated.
    • This is a key governance difference for money-moving actions: a leaked Alpaca key can trade immediately; a Naive agent’s trades are subject to Account Kit policy and optional approval.

Gain #3 — unified accountability

  • Every connect, order, cancel, and close for a customer lands in one per-user activity log — alongside their card, email, vault, and KYC events, not in a separate Alpaca dashboard:
  • That is the question that is hard to answer when trading lives in Alpaca, cards live in Stripe, and secrets live somewhere else. Under Naive it is a single query.

What does not map yet

A migration guide that hides gaps is worse than none. The core path (connect → read → place/cancel/close) maps cleanly across stocks, options, and crypto, but the following Alpaca capabilities have no direct equivalent on Naive’s trading primitive today. Check this list against your app before you commit.
If your agent needs the Broker API (you open and fund accounts for users), real-time fill streaming, historical market data, or order replace, those are the gaps most likely to matter — Naive proxies the Alpaca Trading API v2 for an account the user connects, not a brokerage you operate. Equally, note the model inversion: Alpaca’s direct key trades the instant it is used, while Naive routes every money-moving action through an Account Kit that can freeze it for human approval. That governance is the gain — but it is a real behavior change your agent code must handle (isPendingApproval + approvals.wait).

Where to go next