Raxx · internal docs

internal · gated ↑ index

Fidelity Broker API Integration — Architecture Design

Status: Draft
Date: 2026-05-05
Author: software-architect
Parent epic: (to be filed by product-manager)
BLR parallel track: docs/legal/research/fidelity-api-integration-2026-05-05.md
ADRs produced: 0050-fidelity-api-surface-choice, 0051-fidelity-auth-flow, 0052-broker-adapter-interface


1. Context

Raxx confirmed a Hybrid BYOB strategy (2026-04-29): Alpaca as default, plus an aggregator and first-party broker integrations in the roster. Fidelity is the first first-party addition under evaluation. It is a high-value target — largest retail custodian by AUM in the US, large overlap with the active-trader persona Raxx targets.

This design covers the technical side of the integration. The parallel BLR track covers legal access eligibility, partner agreement requirements, and regulatory posture. Neither track produces implementation code until both tracks converge with approval.

The integration must be:


2. Invariants

All platform invariants apply. Those with heightened relevance to this design:

# Invariant Implications here
I1 No stored credentials. Customer's Fidelity session tokens stored only as envelope-encrypted ciphertext if OAuth is used; never plaintext at rest.
I2 Passkeys only for Raxx auth. Fidelity OAuth does not change Raxx's own auth surface. Step-up WebAuthn required before every live order submit.
I3 Paper-first gating. Fidelity live-handoff requires paper-profitable-for-N-cycles gate or explicit per-flow audited override.
I4 Audit trail for state changes affecting money / permissions. Every Fidelity connection lifecycle event + every order submit writes an audit_log row.
I5 GDPR by default. Fidelity OAuth tokens (if stored) are PII-adjacent; erasure on DSR must reach Fidelity (revoke upstream) and our DB.
I6 Credentials into infra, not code. Fidelity OAuth client_id + client_secret live in Infisical. Rotatable without redeploy.
I7 Kill switch on live execution paths. FIDELITY_LIVE_HANDOFF_DISABLED=1 must block new connections and new live order submissions.

3. Fidelity API Surface — Decision Matrix

This is the top-level architectural question that must be answered before implementation begins. BLR findings determine what is actually accessible. This section scores each candidate surface so operator + BLR can converge on one.

3a. Candidate surfaces

Surface Description Order capability Auth UX Data freshness Partner friction
Wealthscape Integration Xchange (WIX) Fidelity's institutional / RIA API program. REST + FIX. Full order lifecycle. Full trade lifecycle (read + trade + corporate actions) Redirect OAuth; customer authorizes via Fidelity institutional login WebSocket available for institutional tier High — formal partnership, RIA/custodial relationship required, legal agreement, onboarding fee likely
Active Trader Pro (ATP) unofficial No documented public API. ATP's local API is reverse-engineered / community. Read + some order submission (unofficial) Proprietary token; not OAuth Polling or local hooks Not a viable path — no partner agreement possible, ToS violation risk
Brokerage Developer Portal (FDX / Open Finance) Open Finance Data Exchange (FDX) spec. Primarily read-focused (accounts, positions, transactions). FDX v6+ has limited payment initiation. Primarily read-only; limited write (ACH, no securities orders in FDX 6.0) 3-legged OAuth 2.0 (PKCE). Customer redirects to Fidelity.com Polling (no WebSocket in FDX standard) Medium — formal app registration, FDX member compliance, but no RIA requirement
eMoney / Fidelity Wealth API Aimed at financial planning software, not trading platforms. No order submission OAuth Data aggregation only Not relevant — targets wealth planning, not execution

3b. Recommendation

Primary target: Wealthscape Integration Xchange (WIX) — contingent on BLR confirming access eligibility.

Rationale: WIX is the only surface that provides the full trade lifecycle (order submit, bracket, OCO, fills, position management) at institutional quality. FDX is read-only for securities — unsuitable for order routing. ATP unofficial path is a ToS violation and cannot be a supported integration.

Fallback if WIX is inaccessible: FDX for account/portfolio read + hold live trading pending a later WIX agreement.

This is the highest-priority open question for BLR. See §9.

3c. WIX-specific posture

Based on publicly available Fidelity developer documentation and industry knowledge as of 2026-05-05:

BLR must confirm: eligibility, agreement requirements, timeline, and whether a "pending partnership" sandbox exists before Raxx commits engineering.


4. Data Model

4a. fidelity_live_connections table

Mirrors the pattern from alpaca_live_connections (ADR 0014). One active connection per user maximum.

CREATE TABLE fidelity_live_connections (
    id                         TEXT PRIMARY KEY,          -- uuid v4
    user_id                    TEXT NOT NULL
                                   REFERENCES users(id) ON DELETE CASCADE,
    broker_account_id          TEXT NOT NULL,             -- Fidelity account identifier, opaque
    scopes                     TEXT NOT NULL,             -- space-separated OAuth scopes
    access_token_ciphertext    BLOB NOT NULL,             -- envelope-encrypted; NO plaintext
    access_token_iv            BLOB NOT NULL,
    access_token_wrapped_dek   BLOB NOT NULL,
    kms_key_id                 TEXT NOT NULL,
    refresh_token_ciphertext   BLOB,                      -- if issued
    refresh_token_iv           BLOB,
    refresh_token_wrapped_dek  BLOB,
    issued_at                  TIMESTAMP NOT NULL,
    expires_at                 TIMESTAMP NOT NULL,
    last_used_at               TIMESTAMP,
    needs_reauth               BOOLEAN NOT NULL DEFAULT 0,
    revoked_at                 TIMESTAMP,
    disconnected_at            TIMESTAMP,                 -- customer-side revoke detected
    CHECK (length(access_token_ciphertext) > 0),
    UNIQUE (user_id) WHERE revoked_at IS NULL AND disconnected_at IS NULL
);

CREATE INDEX fidelity_live_connections_user_idx
    ON fidelity_live_connections(user_id)
    WHERE revoked_at IS NULL AND disconnected_at IS NULL;

Forbidden columns (CI-grep check): access_token TEXT, access_token VARCHAR, refresh_token TEXT, refresh_token VARCHAR. The plaintext columns must never exist.

4b. broker_order_log table (unified, not Fidelity-specific)

The BrokerAdapter abstraction (§5) routes orders from multiple brokers. A unified order-log table spans adapters, scoped by broker_id. This avoids per-broker log tables as the roster grows.

CREATE TABLE broker_order_log (
    id                 TEXT PRIMARY KEY,                  -- uuid v4
    broker_id          TEXT NOT NULL,                     -- 'alpaca' | 'fidelity' | ...
    user_id            TEXT NOT NULL
                           REFERENCES users(id),
    raxx_order_id      TEXT NOT NULL,                     -- Raxx internal order UUID
    broker_order_id    TEXT,                              -- broker-assigned ID after submit
    status             TEXT NOT NULL,                     -- 'pending'|'submitted'|'filled'|'rejected'|'cancelled'
    order_spec_hash    TEXT NOT NULL,                     -- SHA-256 of serialized OrderRequest; for idempotency
    submitted_at       TIMESTAMP,
    filled_at          TIMESTAMP,
    rejected_reason    TEXT,
    audit_event_id     TEXT REFERENCES audit_log(id),
    created_at         TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

5. Broker Adapter Interface

The key architectural decision (ADR 0052): introduce a BrokerAdapter abstract base class. AlpacaBrokerAdapter and FidelityBrokerAdapter both implement it. A service-locator (BrokerAdapterRegistry) resolves the correct adapter per user at runtime.

5a. Pseudo-Python interface sketch

# backend_v2/api/services/broker/base.py  (new)

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional

@dataclass
class OrderRequest:
    raxx_order_id: str           # our idempotency key
    symbol: str
    side: str                    # 'buy' | 'sell'
    qty: float
    order_type: str              # 'market' | 'limit' | 'stop' | 'stop_limit' | 'trailing_stop'
    time_in_force: str           # 'day' | 'gtc' | 'ioc' | 'fok'
    limit_price: Optional[float]
    stop_price: Optional[float]
    trail_amount: Optional[float]
    trail_percent: Optional[float]
    legs: Optional[list["OrderRequest"]]   # for bracket / OCO

@dataclass
class OrderResult:
    broker_order_id: Optional[str]
    status: str                  # 'submitted' | 'rejected' | 'error'
    rejected_reason: Optional[str]
    idempotent_duplicate: bool   # True if broker confirmed this was a duplicate

@dataclass
class Position:
    symbol: str
    qty: float
    avg_entry: float
    current_price: Optional[float]

class BrokerAdapter(ABC):
    broker_id: str               # class-level constant, e.g. 'fidelity'

    @abstractmethod
    def submit_order(self, order: OrderRequest, user_id: str) -> OrderResult: ...

    @abstractmethod
    def cancel_order(self, broker_order_id: str, user_id: str) -> bool: ...

    @abstractmethod
    def get_order_status(self, broker_order_id: str, user_id: str) -> dict: ...

    @abstractmethod
    def get_positions(self, user_id: str) -> list[Position]: ...

    @abstractmethod
    def get_account(self, user_id: str) -> dict: ...

    @abstractmethod
    def is_market_hours(self) -> bool: ...

    @abstractmethod
    def health_check(self) -> dict: ...   # for status-page poller

class BrokerAdapterRegistry:
    """Runtime service locator. Resolves adapter per user from their live connection."""

    def get_adapter_for_user(self, user_id: str) -> BrokerAdapter: ...
    def register(self, adapter: BrokerAdapter) -> None: ...

5b. Existing Alpaca service migration path

alpaca_integration.py and trading_runtime.py are NOT changed in this design pass. The refactor into AlpacaBrokerAdapter(BrokerAdapter) is a sub-card (SC-3). The new FidelityBrokerAdapter is built against the interface from the start (SC-6).


6. Auth Flow — WIX OAuth 3-Legged

6a. Onboarding sequence

sequenceDiagram
    participant U as Customer (Antlers)
    participant R as Raptor (backend_v2)
    participant F as Fidelity OAuth
    participant V as Velvet (secret store)

    U->>R: POST /api/broker/fidelity/connect
    Note over R: gate: paper-profitable OR override+step-up WebAuthn
    R->>R: generate state=UUID + PKCE code_verifier
    R->>R: store (state, user_id, code_verifier) in session_store TTL=10min
    R-->>U: redirect_uri → Fidelity OAuth authorization endpoint
    U->>F: browser redirect with client_id, scope, state, code_challenge
    F-->>U: Fidelity login + consent screen
    U->>F: customer authenticates + grants consent
    F-->>U: redirect to Raxx callback: /api/broker/fidelity/callback?code=X&state=Y
    U->>R: GET /api/broker/fidelity/callback?code=X&state=Y
    R->>R: verify state matches session_store (CSRF protection)
    R->>F: POST /oauth/token (code + code_verifier + client_secret from Infisical)
    F-->>R: {access_token, refresh_token, expires_in, scope}
    R->>R: envelope-encrypt access_token + refresh_token (KMS)
    R->>R: INSERT fidelity_live_connections row
    R->>R: write audit_log: FIDELITY_CONNECT, user_id, broker_account_id
    R-->>U: 200 {connected: true, broker_account_id: "..."}
    Note over U: UI shows "Connected" status; broker name not shown

6b. Token refresh sequence

Raptor refreshes proactively when expires_at - now() < REFRESH_WINDOW (default: 15 min). On 401 from Fidelity, sets needs_reauth=true, surfaces re-auth UX to customer.

6c. Customer revocation at Fidelity (disconnect detected by Raptor)

When Fidelity returns a 401 or explicit "revoked" error on an API call:

  1. Set disconnected_at = now() on the fidelity_live_connections row.
  2. Cancel any pending orders in broker_order_log with status='pending' for this user.
  3. Write audit_log: FIDELITY_DISCONNECTED_UPSTREAM, user_id.
  4. Surface "Your connection to your brokerage was disconnected" in Antlers (no Fidelity name).

6d. OAuth client credentials (Raxx-side)

FIDELITY_OAUTH_CLIENT_ID and FIDELITY_OAUTH_CLIENT_SECRET live in Infisical at path /raxx/v1/{env}/fidelity/. Rotatable without redeploy. These are Raxx's platform credentials for the WIX partner registration, not per-customer credentials.


7. Order-Routing Model

7a. OrderRequest → Fidelity API mapping

Raxx's internal OrderRequest (§5a) maps to Fidelity WIX order submission:

Raxx field Fidelity WIX equivalent Notes
symbol symbol (CUSIP or ticker) WIX may require CUSIP for some order types; adapter normalizes
side orderType.action BUY / SELL
qty quantity
order_type='market' MARKET
order_type='limit' LIMIT with limitPrice
order_type='stop' STOP with stopPrice
order_type='stop_limit' STOP_LIMIT with both prices
order_type='trailing_stop' TRAILING_STOP BLR/testing must confirm WIX supports this
time_in_force='day' DAY
time_in_force='gtc' GOOD_TILL_CANCEL
legs (2 legs) BRACKET or OCO order Confirm WIX bracket/OCO support in sandbox

Open question for BLR/WIX onboarding: confirm that WIX supports bracket orders and trailing stops via the REST API (vs FIX only).

7b. Post-fill state retrieval

Preferred: WebSocket subscription to Fidelity order-update stream (if available on WIX REST tier). Fallback: polling at 5s intervals for open orders, 30s for confirmed fills. Order fill webhook from Fidelity (if available) is the most efficient path but requires a public inbound endpoint.

Recommendation: design for polling first (simpler, no inbound webhook infra), migrate to WebSocket/webhook once partnership is confirmed and sandbox validates.

7c. Idempotency on retries

raxx_order_id (uuid v4, generated by Raptor before the first submit attempt) is included in the Fidelity order submission as a client-order-id field (if WIX supports it; BLR to confirm). Raptor tracks order_spec_hash in broker_order_log; on retry, checks for an existing row with the same hash before resubmitting.


8. Market Data

Fidelity WIX provides market data (quotes, depth, options chains) scoped to the customer's account. This is distinct from Raxx's server-side market data layer.

Design decision: Fidelity market data is not integrated into MarketDataHub in v1. Reasons:

  1. Raxx's market data model is server-side, shared across all users via one account (ADR 0014 §3a). Fidelity's data is scoped per-customer OAuth session.
  2. Per-customer data feeds would require per-customer WebSocket connections — a qualitatively different scale problem.
  3. Alpaca's market data tier (SIP/IEX) is already sufficient for MBT and charting.

Fidelity market data is used only for post-fill confirmation and position reconciliation, not as a feed for MBT or charting. This is the same posture as Alpaca live-handoff (live account provides position/order state; shared hub provides market data for MBT).

If the BLR track surfaces a distinct "Fidelity data-only" partnership path (e.g., FDX read-only), this is a separate design track.


9. Velvet Adapter Sketch

Fidelity's Velvet adapter manages Raxx's platform-level OAuth credentials (client_id + client_secret for the WIX app registration), not per-customer tokens. Per-customer tokens are managed by Raptor's token-refresh logic (§6b).

9a. velvet/adapters/fidelity.py sketch

# velvet/adapters/fidelity.py  (stub — feature-developer fills in)

class FidelityOAuthClientAdapter(BusAdapter):
    """
    Manages rotation of Raxx's Fidelity WIX OAuth client secret.

    Stage flow:
      Verify   — POST /oauth/token with current client_secret (client_credentials grant)
                  → confirms client_id+client_secret pair is still valid.
      Mint     — NOT AUTOMATABLE: Fidelity does not expose a client-secret
                  rotation API. New secret must be minted in the Fidelity
                  WIX developer portal by an operator.
                  → pre-staged-mint flow (same pattern as PostmarkSenderTokenAdapter).
      Distribute — InfisicalWriteAdapter writes new secret to vault; this
                    adapter validates the new secret is live against Fidelity.
      Revoke   — Operator confirms old secret revoked in WIX portal; this adapter
                  cannot automate revocation.

    RotationContext.new_value = the new client_secret (pre-staged by operator).
    RotationContext.credential_name = 'FIDELITY_OAUTH_CLIENT_SECRET'

    Env vars read at push() time (never at module load):
      FIDELITY_OAUTH_CLIENT_ID       — from Infisical /raxx/v1/{env}/fidelity/
      FIDELITY_OAUTH_CLIENT_SECRET   — current (old) secret, pre-staged rotation

    Security invariants:
      - client_secret is NEVER logged. SHA-256[:12] only in audit.
      - AdapterResult.error_message MUST NOT contain credential values.

    Feature flag:
      FLAG_VELVET_FIDELITY_ADAPTER=1 to enable network calls.
    """
    broker_id = "fidelity"

    def push(self, credential_name, new_value, context) -> AdapterResult:
        self._assert_flag_on()
        # 1. Verify new_value is live: POST /oauth/token (client_credentials)
        # 2. On success: return AdapterResult(ok=True)
        # 3. On failure: return AdapterResult(ok=False, error_message=...)
        ...

The RotationContext fields needed:

Field Value
credential_name FIDELITY_OAUTH_CLIENT_SECRET
new_value Pre-staged new secret (operator-minted in WIX portal)
env prod or staging
job_id UUID from rotation_jobs.id
rotate_timestamp UTC ISO-8601

10. Failure Modes

Failure Raptor behavior Customer-facing Kill switch
Fidelity API rate limit (429) Exponential backoff (3 retries, max 30s). If all retries exhausted, order rejected with status='error', audit log row written. "Your order could not be submitted. Please try again in a few minutes." FIDELITY_LIVE_HANDOFF_DISABLED=1
Fidelity partial outage (5xx on order submit) Same backoff as rate limit. Paper-mode fallback is NOT offered (paper is MBT, not Fidelity). Orders are queued in broker_order_log with status='pending' for operator review. Status page trade-execution → degraded. "Order submission delayed." FIDELITY_LIVE_HANDOFF_DISABLED=1
Fidelity full outage Same as above. No new live orders submitted. Existing positions unaffected (Fidelity holds them). Status page degraded. Console auto-creates FreeScout ticket (existing "Investigate" pattern). FIDELITY_LIVE_HANDOFF_DISABLED=1
Customer OAuth token expired Raptor proactive refresh (§6b). If refresh fails, needs_reauth=true. Subsequent order attempts return HTTP 403 to Antlers. "Reconnect your brokerage to resume live trading." Re-auth flow triggered. Same
Customer revokes access at Fidelity Detected on next API call (401). disconnected_at set. Pending orders cancelled. Audit log written. "Your brokerage connection was disconnected. No orders will be submitted until you reconnect." Same
Network partition (Raptor can't reach Fidelity) Same as 5xx. Backoff, queue, surface to status page. Status page degraded. Same
KMS unavailable (can't decrypt token) Order rejected immediately. Audit log written with KMS_UNAVAILABLE. No retry (KMS unavailability is not transient on the seconds scale). "Service temporarily unavailable." Separate KMS health gate

11. Status-Page Integration

Add a sub-entry under the existing broker-connectivity surface. Per existing posture, partner_name stays null publicly.

# config/status-surfaces.yaml — proposed addition

  - id: broker-fidelity-connectivity
    display_name: "Brokerage Connection"
    category: downstream_3p
    probe_url: null
    partner_status_url: null   # Fidelity does not expose a public machine-readable status API
                               # as of 2026-05-05; manual operator update or polling fidelity.com
                               # HTTP probe if it proves stable. Flag for BLR/partnership track.
    partner_name: null         # intentionally null — generic copy only per posture
    public_description: "Connection to your brokerage for order routing and account data."
    feature_flag: fidelity_live_handoff   # not shown on status page until flag is GA

Fidelity public status: Fidelity does not expose a machine-readable status API (e.g., Atlassian statuspage JSON) as of 2026-05-05. The partner_poller would need to probe https://www.fidelity.com/ or a known API endpoint directly for an HTTP 200 as a liveness proxy. This is a weak signal — treat as "no public status URL" until the partnership produces a dedicated status endpoint.


12. Testing Strategy

12a. Sandbox

WIX provides a sandbox environment for registered partners. Access requires the formal partnership agreement. Until that is in place:

12b. Test fixtures

Following the Alpaca pattern (backend_v2/tests/):

12c. lintNoBrokerNames test

The existing lintNoBrokerNames.test.js already covers fidelity as a banned string in customer-facing copy. No change needed — the lint test enforces the brand-invisible posture automatically.


13. Migrations

13a. Schema migrations required

Migration Description Rollback
M1: broker_order_log table Create unified order log table (§4b). Additive. DROP TABLE broker_order_log
M2: fidelity_live_connections table Create per-user Fidelity connection table (§4a). Additive. DROP TABLE fidelity_live_connections
M3: audit_log event types Add FIDELITY_CONNECT, FIDELITY_DISCONNECT, FIDELITY_TOKEN_REFRESH, FIDELITY_ORDER_SUBMIT to event-type enum or constraint. Remove enum values (safe if no rows use them yet)

All migrations are additive. No existing tables are altered. Rollback is DROP TABLE on the new tables, safe to run at any point before GA.

13b. No data migration needed

The Alpaca alpaca_live_connections table is not touched. The new broker_order_log starts empty.


14. Rollout Plan

dark (flag off)     — schema migrations run; adapter code ships; no customer exposure
↓
flag: fidelity_live_handoff=false (default)
↓
alpha (operator + internal accounts only)
  - FIDELITY_SANDBOX_URL set; integration tests run against WIX sandbox
  - Paper-first gate active; only manually-overridden test accounts can connect
↓
beta (Pro+ invite-only cohort, <25 users)
  - WIX production credentials active (FIDELITY_OAUTH_CLIENT_ID/SECRET from Infisical)
  - paper-profitable-for-N gate enforced (not override)
  - Status-page broker-fidelity-connectivity surface active
  - Console kill switch `FIDELITY_LIVE_HANDOFF_DISABLED` live
↓
GA (flag promoted to prod default)
  - Full Pro+ access
  - Velvet adapter for FIDELITY_OAUTH_CLIENT_SECRET rotation active

Feature flags required: - fidelity_live_handoff — controls customer-facing connect flow + order submission path - fidelity_adapter_velvet — controls Velvet adapter push() calls (separate from live handoff)

Both flags managed via /console/flags with mark-promote → promote workflow (existing console flag UI).


15. Security Considerations

PII collected: - Fidelity broker_account_id (opaque account identifier) — stored in fidelity_live_connections. - OAuth access_token and refresh_token — stored encrypted at rest (envelope encryption, KMS-backed). - Neither is a "credential" in the replay sense of invariant I1 if encrypted with non-exportable KMS key and no plaintext column exists.

Retention: - fidelity_live_connections rows retained for 90 days after revoked_at or disconnected_at (audit trail need). Purged by the existing retention job. - broker_order_log rows retained for 7 years (US securities recordkeeping) regardless of account deletion. This is an intentional carve-out from GDPR erasure — justified by legal retention obligation; must be documented in the GDPR records-of-processing.

DSR (data subject request): - Access: return fidelity_live_connections metadata (not the decrypted tokens) + broker_order_log rows. - Erasure: revoke upstream OAuth at Fidelity (where possible via /token/revoke), then delete fidelity_live_connections row. broker_order_log rows are retained under legal retention carve-out but anonymized (user_id nulled, broker_account_id nulled) after retention period. - Portability: order history exported as CSV on demand.

Audit log: - Every connection lifecycle event + every order submit is an audit_log row. - No credential values in audit. SHA-256 prefix only. - Retention: 7 years (same as broker_order_log). - Access to audit rows: scoped to active support ticket per existing workflow-uuid-tracing design.

Credential replay protection: - Envelope-encrypted at rest. KMS wrapping key not exportable. Decrypted value exists only in process memory during the request. - If KMS key is compromised, rotation revokes + re-encrypts all rows (sub-card SC-9, future).

Breach notification: - If fidelity_live_connections ciphertext is exfiltrated, GDPR 72-hour notification clock starts. KMS key status determines whether breach is material (encrypted without KMS key = low risk; if KMS is also compromised = high risk). Breach response procedure already documented in platform GDPR posture.

Secrets rotation: - FIDELITY_OAUTH_CLIENT_ID + FIDELITY_OAUTH_CLIENT_SECRET in Infisical. Rotatable without redeploy. Velvet adapter (§9) automates verification; rotation of the secret in the WIX portal is manual (pre-staged-mint pattern).

Kill switches: - FIDELITY_LIVE_HANDOFF_DISABLED=1 — blocks new connections + new live order submissions. Set in Infisical, propagated to Heroku config-vars by Velvet (no redeploy needed). - FIDELITY_ADAPTER_VELVET_DISABLED=1 — disables Velvet adapter push() without disabling live handoff.


16. Open Questions

  1. [BLOCKS ALL ENGINEERING] Is Raxx eligible for WIX partnership? What is the minimum entity/AUM/user requirement? What is the application timeline?
  2. Does WIX require Raxx to be a registered RIA or BD? If so, what is the path and timeline?
  3. Does FDX (read-only) have a lighter-weight agreement path that could unblock a "portfolio view" feature before full order-routing?
  4. Does the WIX partner agreement permit Raxx to store encrypted OAuth tokens (access + refresh) at rest on our infrastructure? Or is "no token storage" a contractual requirement?
  5. Does Fidelity require specific data residency (US-only) for customer token storage? Does Heroku's hosting satisfy that requirement?
  6. Is bracket order / OCO support available in WIX REST (vs FIX only)?
  7. Is trailing stop order type available in WIX REST?
  8. What is the WIX sandbox availability policy — available before agreement finalized, or only post-agreement?
  9. Are there prohibited customer segments in the WIX partner agreement (e.g., non-US residents, specific account types)?
  10. What breach notification obligations does the WIX partner agreement impose beyond GDPR's 72-hour rule?

For operator (product / business)

  1. [BLOCKS BETA] Paper-profitable-for-N-cycles gate: what is N for Fidelity live-handoff? Same N as Alpaca (which needs to be formally set), or different?
  2. Should Fidelity live-handoff be Pro+ only, or also available to Pro tier (same as Alpaca per pricing matrix)?
  3. Is there a target date for WIX partnership application? This sets the engineering timeline more than any technical constraint.
  4. FDX read-only path: is a "connect Fidelity for portfolio view (no trading)" feature worth shipping before WIX order-routing is live?
  5. What is the Velvet rotation schedule for FIDELITY_OAUTH_CLIENT_SECRET once the partnership is live? (90 days same as Alpaca, or partner-agreement-driven?)

17. Proposed Sub-Cards

Feature-developer-sized. Each is one PR.

ID Title Size Blocks
SC-1 Schema migration: create broker_order_log table + fidelity_live_connections table S SC-2, SC-5
SC-2 audit_log migration: add Fidelity event types S SC-5, SC-6
SC-3 Refactor alpaca_integration.py + trading_runtime.py into AlpacaBrokerAdapter(BrokerAdapter) M SC-6 (parallel ok)
SC-4 Define BrokerAdapter ABC + BrokerAdapterRegistry in backend_v2/api/services/broker/ S SC-3, SC-6
SC-5 FidelityBrokerAdapter: OAuth connect flow (callback handler + token storage) M SC-6
SC-6 FidelityBrokerAdapter: order submit + cancel + status poll M
SC-7 FidelityBrokerAdapter: token refresh + needs_reauth surfacing S SC-5
SC-8 FidelityBrokerAdapter: upstream-revoke detection + graceful disconnection flow S SC-5
SC-9 Velvet adapter FidelityOAuthClientAdapter (platform credential rotation) S SC-5 must be in prod first
SC-10 Feature flag wiring: fidelity_live_handoff + console kill switch S SC-4
SC-11 Onboarding wizard integration: "Connect a broker" flow in Antlers (Fidelity slot) M SC-5
SC-12 Status-page entry: broker-fidelity-connectivity surface + poller probe S
SC-13 Integration tests: WIX sandbox fixtures + CI skip gate S SC-6
SC-14 DSR handler: Fidelity data in access/erasure/portability pipeline S SC-1
SC-15 broker_order_log 7-year retention job + anonymization on DSR erasure S SC-1

Total: 15 sub-cards. Order of claim: SC-4 → SC-1 + SC-2 → SC-3 + SC-5 (parallel) → SC-6 through SC-15.

Hard gates before any SC beyond SC-4: - BLR confirms WIX access eligibility (open question BLR-1). - Operator confirms paper-gate N (open question OP-1). - Operator confirms Pro vs Pro+ tier scope (open question OP-2).


End of design doc. See ADRs 0050, 0051, 0052 for individual decision records. Parallel legal research at docs/legal/research/fidelity-api-integration-2026-05-05.md.