Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Consume Alpaca Broker API real-time event streams over Server-Sent Events (SSE) — account status, journal, transfer/funding, trade, and non-trade-activity events — reliably. Covers connection, auth, replay cursors (since/since_id), heartbeats, reconnection/backoff, ordering, and idempotent processing. Use when building an event consumer for Alpaca lifecycle events in any language.
.claude/skills/alpacahq-alpaca-broker-sse-events/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 22% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 17% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -5% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-06 | ✗→✓ | ▲ Improved | -36% | 0% |
Alpaca pushes brokerage lifecycle events over Server-Sent Events: a long-lived HTTP GET that streams text/event-stream. This is not the market-data WebSocket (alpaca-broker-market-data) — different transport, different auth, different reliability model.
> Read alpaca-broker-integration first. SSE uses the Broker API host + HTTP Basic auth (same credential as Broker REST).
https://docs.alpaca.markets/docs/sse-eventsalpaca-docs MCP → search "SSE Events", then fetch us/sse-eventsSSE is plain HTTP. You don't need a special client: open a GET, keep the connection open, and read the body line-by-line. Each event is a data: line containing a JSON object. It is replayable — you can ask for events from a point in the past and seamlessly catch up to live, which makes it far better than naive polling for lifecycle state.
| Stream | Path | Carries | |--------|------|---------| | Account status | GET /v1/events/accounts/status | Account-property changes: status/crypto_status (SUBMITTED→ACTIVE, ACTION_REQUIRED, REJECTED), plus kyc_results, account_blocked, trading_blocked, cash_interest, options | | Journal status | GET /v2/events/journals/status | JNLC/JNLS lifecycle (queued→executed, correct…) | | Funding/transfer status | GET /v2/events/funding/status | Unified: Transfer, BankRelationship, WireBank, FundingWallet entities (switch on entity_type) | | Trade updates | GET /v2/events/trades | Order events in the event field: new, fill, partial_fill, canceled, rejected, held, trade_bust, trade_correct… (richer than order status) | | Non-trade activities | GET /v1/events/nta | Dividends, interest, fees, splits, ACATs, cash disbursements. entry_type e.g. JNLC/FEE/INT/DIVNRA/CSD; status ∈ executed/correct/canceled |
> Paths & versions are NOT uniform — verify each. This is exactly the kind of cross-stream inconsistency Alpaca's docs under-communicate: > - /v2 streams (trades, journals/status, funding/status) use a ULID event_id directly; /v1/events/trades and /v1/events/journals/status are legacy (existing partners only — migrate to v2). /v2/events/trades was previously /v2beta1, now redirected. > - /v1 streams (accounts/status, nta) are current, not deprecated — there is no v2 yet. Each event carries both an integer event_id and a ULID event_ulid. > > Every event carries at (timestamp), account_id, and status_from/status_to (account/journal/funding) or event+order (trades).
GET /v2/events/journals/status?since_id=<last-ulid-you-saw> HTTP/1.1
Host: broker-api.alpaca.markets
Authorization: Basic <base64(key:secret)>
Accept: text/event-streamRead the response stream and parse data: {…} frames as they arrive. In most languages an off-the-shelf EventSource/SSE client works — just make sure it lets you set the Authorization header on the initial request (the browser EventSource API famously does not; use a server-side SSE library instead).
Every stream supports point-in-time replay:
| Param | Meaning | |-------|---------| | since / until | Date or RFC3339 timestamps. URL-encode + in offsets as %2B. | | since_id / until_id | ID cursors. On v2 streams the ID is a ULID. On v1 streams it's the integer event_id. | | since_ulid / until_ulid | v1 streams only (accounts, nta) — ULID-based cursors, since v1 events carry both an int event_id and a event_ulid. |
Rules: since is required if until is set; since_id required if until_id set (same for since_ulid/until_ulid); you can't mix since, since_id, and since_ulid. Without any since cursor, no history is returned — you only get live pushes from now on. Reaching the until bound ends the stream with a 200.
This is the single most important reliability lesson: persist the ID of the last event you successfully processed. On every (re)connect, pass it as your since cursor (since_id on v2; since_ulid or since_id on v1) so Alpaca replays anything you missed during the gap. A consumer that reconnects without a cursor silently drops every event that occurred while it was down.
Within a millisecond, ULIDs contain a random component, so two events in the same millisecond can sort either way. Alpaca's own guidance: for reconciliation, restart the stream from a since a few minutes before your last event and rely on idempotent processing to absorb the overlap. Don't assume strict total ordering — assume approximate ordering plus dedup.
SSE connections drop — networks, load balancers, deploys, and Alpaca-side resets all happen. A production consumer needs:
lastMessageAt on every frame; if the stream is silent past a threshold (e.g. 5 min), proactively tear down and reconnect — a dead socket often looks "open."since_id = last processed event (see §4).> Note: OpenAPI can't fully model SSE, so generated API clients often hang on these endpoints (waiting for a response that never ends). Use a real streaming HTTP/SSE client, not a codegen'd one.
The robust shape for each event:
parse → persist a raw event snapshot (keyed on event_id, skip-if-exists)
→ match the local record by Alpaca ID (account_id / journal_id / order_id / transfer_id)
→ update local state under a row lock / guarded by current status
→ fire side effects (notifications, downstream transfers)
→ advance the stored cursor to this event_idevent_id. Insert the raw event with an upsert/skip-duplicate on event_id. A duplicate (from replay or an at-least-once redelivery) is then a no-op. This is your dedup boundary.SELECT … FOR UPDATE or equivalent) when mutating a transfer/order so two events for the same record can't race.status_to == ACTIVE. Reject sandbox/paper account IDs in live handlers.new/accepted/pending_new are pre-fill; update local order state on fill/partial_fill/canceled/rejected. Invalidate any cached portfolio/holdings on fills.executed isn't final and correct spawns a new journal ID (see alpaca-broker-journals). Idempotency + ID-keyed snapshots absorb both.entity_type. Funding-wallet per-transfer status may still need polling (alpaca-broker-funding-transfers).Even a perfect consumer can miss events (extended downtime beyond retention, a bug, an un-handled type). Always pair SSE with a periodic reconciliation/heal pass that re-pulls authoritative state (activities, journals, transfers) from Alpaca and upserts it. SSE is for low latency; reconciliation is for correctness. See alpaca-broker-reconciliation-idempotency.
Related skills: correctness backstop → alpaca-broker-reconciliation-idempotency; dedup/idempotency mechanics → alpaca-broker-reconciliation-idempotency; backoff details → alpaca-broker-rate-limits-resilience; market-data streaming (WS, not SSE) → alpaca-broker-market-data.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 27,017 | 23,805 | -12% | 1 | 1 | 0% | 5,963 | 7,292 | +22% | 0 | 0 | — |
case-02 | fail→pass | 26,287 | 18,354 | -30% | 1 | 1 | 0% | 5,227 | 6,100 | +17% | 0 | 0 | — |
case-03 | fail→pass | 31,547 | 18,630 | -41% | 1 | 1 | 0% | 6,204 | 5,895 | -5% | 0 | 0 | — |
case-04 | pass→pass | 10,343 | 7,288 | -30% | 1 | 1 | 0% | 1,954 | 3,787 | +94% | 0 | 0 | — |
case-09 | pass→pass | 5,086 | 2,344 | -54% | 1 | 1 | 0% | 895 | 2,717 | +204% | 0 | 0 | — |
case-05 | fail→pass | 8,682 | 3,188 | -63% | 1 | 1 | 0% | 1,745 | 3,113 | +78% | 0 | 0 | — |
case-06 | fail→pass | 23,258 | 3,743 | -84% | 1 | 1 | 0% | 5,008 | 3,216 | -36% | 0 | 0 | — |
case-07 | pass→pass | 12,404 | 7,210 | -42% | 1 | 1 | 0% | 2,196 | 3,647 | +66% | 0 | 0 | — |
case-08 | pass→pass | 8,440 | 3,946 | -53% | 1 | 1 | 0% | 1,889 | 3,246 | +72% | 0 | 0 | — |
case-15 | pass→pass | 12,643 | 7,547 | -40% | 1 | 1 | 0% | 2,238 | 3,738 | +67% | 0 | 0 | — |
case-10 | pass→pass | 6,704 | 2,695 | -60% | 1 | 1 | 0% | 1,220 | 2,728 | +124% | 0 | 0 | — |
case-11 | pass→pass | 17,950 | 9,927 | -45% | 1 | 1 | 0% | 3,035 | 4,169 | +37% | 0 | 0 | — |
case-12 | pass→pass | 3,506 | 1,883 | -46% | 1 | 1 | 0% | 579 | 2,711 | +368% | 0 | 0 | — |
case-13 | fail→pass | 7,706 | 1,483 | -81% | 1 | 1 | 0% | 1,626 | 2,600 | +60% | 0 | 0 | — |
case-14 | pass→pass | 7,571 | 5,821 | -23% | 1 | 1 | 0% | 1,353 | 2,929 | +116% | 0 | 0 | — |
case-16 | pass→pass | 5,199 | 5,778 | +11% | 1 | 1 | 0% | 1,029 | 3,477 | +238% | 0 | 0 | — |
case-17 | fail→fail | 7,096 | 4,399 | -38% | 1 | 1 | 0% | 1,329 | 2,683 | +102% | 0 | 0 | — |
case-18 | pass→pass | 7,840 | 2,698 | -66% | 1 | 1 | 0% | 1,363 | 3,003 | +120% | 0 | 0 | — |
case-19 | pass→pass | 8,662 | 7,570 | -13% | 1 | 1 | 0% | 1,640 | 3,586 | +119% | 0 | 0 | — |
case-20 | fail→pass | 8,630 | 4,715 | -45% | 1 | 1 | 0% | 1,785 | 3,235 | +81% | 0 | 0 | — |
case-21 | pass→pass | 10,607 | 8,730 | -18% | 1 | 1 | 0% | 2,136 | 4,160 | +95% | 0 | 0 | — |
case-22 | pass→pass | 8,429 | 3,692 | -56% | 1 | 1 | 0% | 1,579 | 3,152 | +100% | 0 | 0 | — |
case-23 | pass→pass | 7,992 | 4,442 | -44% | 1 | 1 | 0% | 1,240 | 3,249 | +162% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +30 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.