WebSocket stream
One connection, filtered on the server. Sources are polled every second, and articles are pushed as soon as they are ingested. The summary is not on this socket yet. Included in the Pro plan.
Endpoints
| Socket | When the article arrives |
|---|---|
wss://ws.marketnewsapi.com/v1/stream | As soon as the article is ingested. summary is null. |
wss://ws.marketnewsapi.com/v1/stream/summaries | A few seconds later, after the AI summary is written. The welcome message says this socket is delayed. |
Both sockets use the same channels, the same message types, and the same connection limit. A summaries connection counts as one of the 3 connections on the account.
wss://ws.marketnewsapi.com/v1/stream?api_key=YOUR_KEYsummaries
wss://ws.marketnewsapi.com/v1/stream/summaries?api_key=YOUR_KEYAuthentication
Pass your key as the api_key query parameter. Or connect without it and send an auth message as your first message within 10 seconds:
{
"action": "auth",
"api_key": "mna-live-..."
}On success you get a welcome message. stream and message say which socket you opened:
/v1/stream
{
"type": "welcome",
"authenticated": true,
"plan": "pro",
"connection_id": "c_7Hk2pQ9x",
"max_subscriptions": 200,
"heartbeat_seconds": 30,
"stream": "immediate",
"message": "Articles are sent as soon as they are ingested. This socket does not wait for an AI summary. summary is null until it is written. GET /v1/news/{id} returns the summary once it is stored."
}/v1/stream/summaries
{
"type": "welcome",
"authenticated": true,
"plan": "pro",
"connection_id": "c_7Hk2pQ9x",
"max_subscriptions": 200,
"heartbeat_seconds": 30,
"stream": "summaries",
"message": "This stream waits for the AI summary, so articles arrive a few seconds after they are ingested. The same article is already on /v1/stream and on GET /v1/news without waiting."
}Subscribe
{
"action": "subscribe",
"channels": [
"ticker:AAPL",
"type:press_release",
"keyword:guidance"
]
}server reply
{
"type": "subscribed",
"channels": [
"ticker:AAPL",
"type:press_release",
"keyword:guidance"
],
"total": 3
}Then articles arrive as they're published:
article message
{
"type": "article",
"matched": [
"ticker:AAPL"
],
"data": {
"id": "art_9f2c1d7e4b8a6035",
"title": "Apple unveils new on-device AI features, shares rise in premarket trading",
"url": "https://www.example-publisher.com/markets/apple-ai-features",
"source": {
"name": "Example Publisher",
"domain": "example-publisher.com"
},
"publisher": "Example Publisher",
"published_at": "2026-10-06T12:31:04Z",
"ingested_at": "2026-10-06T12:31:09Z",
"category": "stocks",
"type": "news",
"tickers": [
"AAPL"
],
"listings": [
{
"symbol": "AAPL",
"exchange": "NASDAQ"
}
],
"snippet": "Apple on Tuesday introduced a set of on-device AI features for iPhone and Mac, sending the stock higher before the open.",
"summary": "Apple announced new on-device AI features for iPhone and Mac. Shares rose about 2% in premarket trading as analysts called the update a catalyst for the upgrade cycle.",
"body": null,
"entities": [
{
"type": "company",
"name": "Apple Inc.",
"symbol": "AAPL"
},
{
"type": "product",
"name": "iPhone"
}
],
"language": "en"
}
}Complete examples
# brew install websocat (or: cargo install websocat)
websocat "wss://ws.marketnewsapi.com/v1/stream?api_key=$MARKETNEWSAPI_KEY"
# then paste:
{"action":"subscribe","channels":["ticker:AAPL","type:press_release","keyword:guidance"]}# pip install websockets
import asyncio
import json
import os
import websockets
URL = f"wss://ws.marketnewsapi.com/v1/stream?api_key={os.environ['MARKETNEWSAPI_KEY']}"
async def main():
async for ws in websockets.connect(URL): # reconnects automatically
try:
await ws.send(json.dumps({"action": "subscribe", "channels": ["ticker:AAPL", "type:press_release", "keyword:guidance"]}))
async for raw in ws:
msg = json.loads(raw)
if msg["type"] == "article":
print(msg["data"]["published_at"], msg["data"]["title"])
except websockets.ConnectionClosed:
continue
asyncio.run(main())// Browsers and Node.js 22+ ship a global WebSocket.
const ws = new WebSocket(`wss://ws.marketnewsapi.com/v1/stream?api_key=${process.env.MARKETNEWSAPI_KEY}`);
ws.addEventListener("open", () => {
ws.send(JSON.stringify({ action: "subscribe", channels: ["ticker:AAPL", "type:press_release", "keyword:guidance"] }));
});
ws.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "article") console.log(msg.data.published_at, msg.data.title);
});
ws.addEventListener("close", (event) => console.log("closed", event.code, event.reason));// go get github.com/gorilla/websocket
package main
import (
"fmt"
"os"
"github.com/gorilla/websocket"
)
type message struct {
Type string `json:"type"`
Data struct {
Title string `json:"title"`
PublishedAt string `json:"published_at"`
} `json:"data"`
}
func main() {
url := "wss://ws.marketnewsapi.com/v1/stream?api_key=" + os.Getenv("MARKETNEWSAPI_KEY")
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
panic(err)
}
defer conn.Close()
conn.WriteJSON(map[string]any{
"action": "subscribe",
"channels": []string{"ticker:AAPL", "type:press_release", "keyword:guidance"},
})
for {
var msg message
if err := conn.ReadJSON(&msg); err != nil {
panic(err)
}
if msg.Type == "article" {
fmt.Println(msg.Data.PublishedAt, msg.Data.Title)
}
}
}<?php
// composer require phrity/websocket
require 'vendor/autoload.php';
use WebSocket\Client;
$client = new Client('wss://ws.marketnewsapi.com/v1/stream?api_key=' . getenv('MARKETNEWSAPI_KEY'));
$client->text(json_encode([
'action' => 'subscribe',
'channels' => ['ticker:AAPL', 'type:press_release', 'keyword:guidance'],
]));
while (true) {
$msg = json_decode($client->receive()->getContent(), true);
if (($msg['type'] ?? '') === 'article') {
echo $msg['data']['published_at'] . ' ' . $msg['data']['title'] . PHP_EOL;
}
}# gem install websocket-client-simple
require "json"
require "websocket-client-simple"
url = "wss://ws.marketnewsapi.com/v1/stream?api_key=#{ENV.fetch('MARKETNEWSAPI_KEY')}"
ws = WebSocket::Client::Simple.connect(url)
ws.on :open do
ws.send({ action: "subscribe", channels: ["ticker:AAPL", "type:press_release", "keyword:guidance"] }.to_json)
end
ws.on :message do |event|
msg = JSON.parse(event.data)
puts "#{msg['data']['published_at']} #{msg['data']['title']}" if msg["type"] == "article"
end
loop { sleep 1 }using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
var key = Environment.GetEnvironmentVariable("MARKETNEWSAPI_KEY");
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri($"wss://ws.marketnewsapi.com/v1/stream?api_key={key}"), CancellationToken.None);
var sub = JsonSerializer.Serialize(new { action = "subscribe", channels = new[] { "ticker:AAPL", "type:press_release", "keyword:guidance" } });
await ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(sub)),
WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = new byte[64 * 1024];
while (ws.State == WebSocketState.Open)
{
using var ms = new MemoryStream();
WebSocketReceiveResult result;
do
{
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
ms.Write(buffer, 0, result.Count);
} while (!result.EndOfMessage);
using var doc = JsonDocument.Parse(ms.ToArray());
var root = doc.RootElement;
if (root.GetProperty("type").GetString() == "article")
{
var data = root.GetProperty("data");
Console.WriteLine($"{data.GetProperty("published_at")} {data.GetProperty("title")}");
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;
public class Stream {
public static void main(String[] args) throws Exception {
String url = "wss://ws.marketnewsapi.com/v1/stream?api_key=" + System.getenv("MARKETNEWSAPI_KEY");
CountDownLatch closed = new CountDownLatch(1);
WebSocket ws = HttpClient.newHttpClient().newWebSocketBuilder()
.buildAsync(URI.create(url), new WebSocket.Listener() {
private final StringBuilder buf = new StringBuilder();
@Override
public CompletionStage<?> onText(WebSocket socket, CharSequence data, boolean last) {
buf.append(data);
if (last) {
System.out.println(buf); // parse with Jackson or Gson
buf.setLength(0);
}
socket.request(1);
return null;
}
@Override
public CompletionStage<?> onClose(WebSocket socket, int code, String reason) {
closed.countDown();
return null;
}
@Override
public void onError(WebSocket socket, Throwable error) {
error.printStackTrace();
closed.countDown();
}
}).join();
ws.sendText("{\"action\":\"subscribe\",\"channels\":[\"ticker:AAPL\",\"type:press_release\",\"keyword:guidance\"]}", true);
closed.await();
}
}Limits
- 3 concurrent connections per account on the Pro plan. A 4th connection is closed with code
4008. - Up to 200 channels per connection.
- Unlimited messages. No per-message billing.
- Up to 20 control messages (subscribe, ping and so on) per second.
Next: channels, message reference, best practices.