Mode: operator-assisted Last validated: 2026-04-24 UTC Validation method: read-only-docs (DO NOT live-rotate without operator presence + paused trading) Average duration: 15m (includes pause + verify + resume cycle) Required role: superadmin
Applies to: ALPACA_LIVE_API_KEY_ID, ALPACA_LIVE_API_SECRET_KEY. Live keys move real money. Treat every rotation as a planned change with a maintenance window.
Critical: Live trading must be paused before rotation. Open orders may need to be canceled or completed. Algorithmic strategies that depend on these keys must be halted, then resumed with the new keys after validation. Do not rotate live keys without the operator on console.
https://app.alpaca.markets/live/dashboard/overview)# Pause live trading via the existing kill-switch
heroku config:set TRADING_LIVE_DISABLED=1 -a raxx-api-prod
# Wait for dyno restart and confirm
sleep 30
curl -sS https://api.raxx.app/health/trading | jq '.live_enabled'
# Expect: false
Confirm no in-flight orders:
curl -sS -H "APCA-API-KEY-ID: $CURRENT_KEY_ID" \
-H "APCA-API-SECRET-KEY: $CURRENT_SECRET" \
https://api.alpaca.markets/v2/orders?status=open | jq '. | length'
# Expect: 0 (or each one will be canceled by the rotation)
Snapshot live account state (audit trail):
curl -sS -H "APCA-API-KEY-ID: $CURRENT_KEY_ID" \
-H "APCA-API-SECRET-KEY: $CURRENT_SECRET" \
https://api.alpaca.markets/v2/account > /tmp/alpaca-live-pre-rotate-$(date -u +%Y%m%dT%H%M%SZ).json
https://app.alpaca.markets/live/dashboard/overview.NEW_KEY_ID="..."
NEW_SECRET="..."
curl -sS -H "APCA-API-KEY-ID: $NEW_KEY_ID" \
-H "APCA-API-SECRET-KEY: $NEW_SECRET" \
https://api.alpaca.markets/v2/account | jq '{status, account_number, equity, cash, buying_power}'
Sanity-check: account_number and equity MUST match the pre-rotation snapshot exactly. If account_number differs, the operator clicked into the wrong environment; STOP, do not propagate.
infisical secrets set ALPACA_LIVE_API_KEY_ID="$NEW_KEY_ID" \
--projectId="$INFISICAL_PROJECT_ID" --env=prod
infisical secrets set ALPACA_LIVE_API_SECRET_KEY="$NEW_SECRET" \
--projectId="$INFISICAL_PROJECT_ID" --env=prod
| Consumer | How |
|---|---|
| Raptor live runtime | heroku config:set ALPACA_LIVE_API_KEY_ID=... ALPACA_LIVE_API_SECRET_KEY=... -a raxx-api-prod |
| MQ-A algo layer | per its own config-var path (Heroku app name varies) |
Do NOT propagate live keys to staging, GitHub Actions, or local dev. Live keys belong only to production runtime.
# Account check via Raptor
curl -sS https://api.raxx.app/api/trading/account?mode=live | jq '{status, account_number}'
# account_number must match snapshot from step 1
# Place a tiny check order at a price far from market (no fill expected) to validate auth
curl -sS -X POST https://api.raxx.app/api/trading/orders \
-H "Content-Type: application/json" \
-d '{"symbol":"AAPL","qty":1,"side":"buy","type":"limit","limit_price":1.00,"time_in_force":"day","mode":"live"}'
# Capture the order ID, then immediately cancel:
curl -sS -X DELETE https://api.raxx.app/api/trading/orders/<id>
# Expect: 204 / order status = canceled
The dashboard regeneration already invalidated the old key pair atomically. Confirm:
curl -sS -o /dev/null -w "%{http_code}\n" \
-H "APCA-API-KEY-ID: $OLD_KEY_ID" \
-H "APCA-API-SECRET-KEY: $OLD_SECRET" \
https://api.alpaca.markets/v2/account
# Expect: 401 / 403
heroku config:set TRADING_LIVE_DISABLED=0 -a raxx-api-prod
# Wait for dyno restart, confirm
sleep 30
curl -sS https://api.raxx.app/health/trading | jq '.live_enabled'
# Expect: true
action: secret.rotate.completed
actor: <superadmin_id>
context: {
"secret_name": "ALPACA_LIVE_API_KEY_ID",
"method": "operator-assisted-dashboard",
"trading_paused_at": "...",
"trading_resumed_at": "...",
"downtime_seconds": <delta>,
"account_number_verified": true
}
If validation in step 3 or 6 fails:
TRADING_LIVE_DISABLED=1).If the wrong environment was rotated (paper instead of live or vice versa), pause both trading modes and consult both runbooks. Audit-log the mis-rotation as a separate event.
account_number invariance is the canary. Always verify before propagating.