manage_push_subscriptions — see Connect via MCP.
Plan: Free and above · Credits: none — managing subscriptions is not a data read
Two doors, two products
There are two subscription endpoints, and they carry different things. They are the same two interfaces as the WebSocket stream.The news door carries no model scoring, by design.
impacts is removed from every news payload before delivery: this door reports what arrived, not what FocusAlpha thinks of it. If you want scored push, use the events door.Filters
A subscription can narrow what it receives:symbols— only rows whose payloadsymbolsinclude one of these tickers (case-insensitive).company_ids— only rows whose payloadcompany_idis one of these.label_families— only rows carrying one of these label families, such asearnings,guidanceormerger_acquisition. See Event labels.
Where delivery starts
By default a new subscription starts at the current end of the log: “from now on”. Passfrom_seq to replay instead, and you receive every matching row with a seq greater than it.
The signing secret is shown once
What a delivery looks like
Each delivery is aPOST to your URL with a JSON body and these headers:
Content-Type: application/jsonX-FA-Signature: t=<unix seconds>,v1=<hex>— the signature, described below.X-FA-Subscription— the subscriptionid.
stream is merged for the events door and raw for the news door. next_after_seq is your position in the log once you have processed this delivery. Each payload is the full message documented on the Events and Raw news pages.
Respond with any 2xx status to acknowledge. Any other status, a redirect, a timeout or a connection error counts as a failed delivery.
Verify the signature
The signature is HMAC-SHA256, keyed with yoursecret, over the string <t>.<raw body>, where t is the timestamp in the header and the raw body is the exact bytes you received. To verify:
- Parse
tandv1fromX-FA-Signature. - Reject the delivery if
tis more than 5 minutes from your current time. This stops a captured delivery from being replayed later. - Compute the HMAC over
<t>.<raw body>and compare it withv1using a constant-time comparison.
Delivery semantics
- At-least-once. The same row can arrive more than once. Dedupe events-door rows on
(event_id, revision)and news-door rows on the payload’sid; both are stable across re-deliveries. - In order. A subscription’s position is one integer,
cursor_seq. Delivery never skips ahead after a failure, so order per subscription is preserved and a retry resumes exactly where delivery stopped. - Filtered rows still move the cursor.
cursor_seqtracks position in the log, not the last row delivered to you. - Freshness is about 2 minutes end to end, because the log is written in 2-minute cycles. Push removes polling; it does not make the underlying data arrive sooner.
Failures and suspension
After a failed delivery, the subscription waits before trying again: 1 minute, then 5 minutes, 30 minutes, 2 hours, and 6 hours for every later retry. A successful delivery resets the count. After 25 consecutive failures the subscription suspends itself.suspended_at and suspended_reason on a listed subscription say so, and consecutive_failures shows the count. Fix your endpoint, then call resume: delivery picks up from the stored cursor and nothing is skipped, so a subscription that was suspended for a long time delivers a burst.
Limits
Each door allows 10 active subscriptions per account. Creating an eleventh returns403; deactivate one first. Suspended subscriptions are still active and count toward the limit. The URL must use https; plain http is rejected with 400.
POST /v1/events/subscriptions
Creates a subscription on the events door.POST /v1/news/subscriptions takes the same body and creates one on the news door.
string
required
The HTTPS endpoint that deliveries POST to. Up to 2,000 characters.
string[]
Only rows naming one of these tickers. Up to 500.
string[]
Only rows about one of these
company_ids. Up to 500.string[]
Only rows carrying one of these label families. Up to 50.
integer
Replay every matching row with a
seq greater than this. Omit to start from now.secret included this one time.
GET /v1/events/subscriptions
Lists your subscriptions on the events door, oldest first.GET /v1/news/subscriptions lists the news door. Each subscription has id, url, stream, symbols, company_ids, label_families, active, cursor_seq, consecutive_failures, suspended_at, suspended_reason and created_at. The secret is never included.
DELETE /v1/events/subscriptions/{id}
Deactivates a subscription on the events door;DELETE /v1/news/subscriptions/{id} does the same on the news door. Deactivation is soft: delivery stops, and the subscription’s cursor and history are kept. Returns { "id": "…", "active": false }. An id that is not one of your subscriptions on that door returns 404.
string
required
The subscription
id from create or list.POST /v1/events/subscriptions/{id}/resume
Lifts a failure suspension on the events door;POST /v1/news/subscriptions/{id}/resume does the same on the news door. It also clears the retry wait and the failure count, and marks the subscription active. Delivery resumes from the stored cursor. Returns { "id": "…", "active": true, "resumed": true }.
string
required
The subscription
id from create or list.The live stream instead of webhooks
If you would rather hold a WebSocket open than receive POSTs,GET /v1/events/stream-token returns a short-lived credential for the live stream, with a filter value per door and a paged catch_up URL for filling gaps after a disconnect. The WebSocket has no server-side filtering by symbol, company or label; webhook subscriptions do. See WebSocket overview for how to connect.