news.markets.sh beta

Deduplicated, quality-scored, entity-tagged financial news, translated within seconds of publication. Streamed over WebSocket, queryable over REST, one API key for both.

beta This service is in beta. Endpoints and payload fields may still change; breaking changes are announced on this page.

Live preview

demo token · no key needed on this page
wss · topic=all 0 messages connecting

Authentication

one key, both surfaces

Your markets.sh API key works on both the socket and the REST endpoint. Pass it as an X-API-Key or Authorization: Bearer header, or as an api_key query parameter on the socket URL. The live preview above runs on a short-lived demo token minted for this page — no key required to watch.

Stream

WebSocket · push · filters update mid-stream
WSS wss://news.markets.sh/ws/news?api_key=msh_live_…&topic=all&replay=100
ParamTypeDescription
api_keystringYour markets.sh API key (required)
topicstringall, tag:{name}, publisher:{domain}, company:{id}
min_qualityint 1-10Minimum quality score
tickerscsve.g. BTC,ETH
sentimentstringpositive / negative / neutral / mixed
countriescsvISO country codes
primary_categorycsve.g. markets,policy
asset_classescsve.g. crypto,equities
content_typecsvContent type filter
replayintReplay up to this many recent matching articles on connect (max 200)

Messages

Server → client
{
  "type": "article",
  "data": { "id": "...", "headline": "...", ... }
}

{
  "type": "heartbeat",
  "ts": "2026-03-16T21:30:00Z"
}
Client → server
// Update filters mid-stream
{
  "type": "filter",
  "data": { "min_quality": 7, "primary_category": ["markets"] }
}

// Keepalive ping
{ "type": "ping" }

Code examples

const API_KEY = "msh_live_a1b2c3d4e5f6...";
const ws = new WebSocket("wss://news.markets.sh/ws/news?api_key=" + API_KEY + "&topic=all&min_quality=5&replay=100");

ws.onopen = () => console.log("connected");

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.type === "article") {
    console.log(msg.data.headline);
  }
};

// Update filters mid-stream
ws.send(JSON.stringify({
  type: "filter",
  data: { tickers: ["BTC"], min_quality: 7, primary_category: ["markets"] }
}));
import Foundation

let apiKey = "msh_live_a1b2c3d4e5f6..."
let url = URL(string: "wss://news.markets.sh/ws/news?api_key=\(apiKey)&topic=all&replay=100")!
let task = URLSession.shared.webSocketTask(with: url)
task.resume()

func receive() {
  task.receive { result in
    switch result {
    case .success(let message):
      if case .string(let text) = message { print(text) }
      receive() // continue listening
    case .failure(let error):
      print("Error: \(error)")
    }
  }
}
receive()
import asyncio, websockets, json

async def stream():
    api_key = "msh_live_a1b2c3d4e5f6..."
    uri = "wss://news.markets.sh/ws/news?api_key=" + api_key + "&topic=all&min_quality=5&replay=100"
    async with websockets.connect(uri) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "article":
                print(msg["data"]["headline"])

asyncio.run(stream())
# Install: npm i -g wscat
API_KEY="msh_live_a1b2c3d4e5f6..."

wscat -c "wss://news.markets.sh/ws/news?api_key=$API_KEY&topic=all&replay=100"

# With filters
wscat -c "wss://news.markets.sh/ws/news?api_key=$API_KEY&topic=all&min_quality=7&tickers=BTC,ETH&primary_category=markets"

# Tag-specific topic
wscat -c "wss://news.markets.sh/ws/news?api_key=$API_KEY&topic=tag:bitcoin"

# Send filter update once connected
> {"type":"filter","data":{"min_quality":8,"primary_category":["markets"],"asset_classes":["crypto"]}}

Query

REST v3 · pull · search the indexed archive

The same articles the socket pushes, queryable seconds later: full-text search plus every filter the stream supports. Results come back newest-first, or by relevance when q is present.

GET https://markets.sh/api/v3/news?q=nvidia&min_quality=6&limit=5
ParamTypeDescription
qstringKeyword query across headline, summary, and body
symbolscsvTickers resolved to company ids, e.g. NVDA,ASML
tickerscsvDirect ticker filter (no resolution)
tagscsve.g. bitcoin,ecb
primary_categorycsve.g. markets,policy
asset_classescsve.g. crypto,equities
countriescsvISO country codes
sentimentstringpositive / negative / neutral / mixed
min_qualityint 1-10Minimum quality score
sources / exclude_sourcescsvPublisher domains to include or exclude
languagecsvISO language codes
from / toISO 8601Published-at range
fieldscsvReturn only these fields; add body to include article bodies
limit / offsetintDefault 15 per page; offset + limit capped at 1000

Response

{
  "data": [
    {
      "id": "...",
      "headline": "...",
      "summary": "...",
      "quality_score": 8,
      "primary_category": "markets",
      "asset_classes": ["equities"],
      "tickers": ["NVDA"],
      "tags": ["semiconductors"],
      "publisher_domain": "...",
      "published_at": "2026-08-12T14:03:00Z"
    }
  ],
  "pagination": {
    "total": 412, "limit": 15, "offset": 0,
    "count": 15, "has_more": true, "next_offset": 15
  }
}

Code examples

API_KEY="msh_live_a1b2c3d4e5f6..."

# Search the archive
curl -s "https://markets.sh/api/v3/news?q=nvidia&min_quality=6&limit=5" \
  -H "X-API-Key: $API_KEY"

# Same filters as the stream
curl -s "https://markets.sh/api/v3/news?tags=bitcoin&asset_classes=crypto&sentiment=positive" \
  -H "X-API-Key: $API_KEY"

# Include article bodies
curl -s "https://markets.sh/api/v3/news?symbols=NVDA&fields=headline,body,published_at" \
  -H "X-API-Key: $API_KEY"
const res = await fetch(
  "https://markets.sh/api/v3/news?q=nvidia&min_quality=6&limit=5",
  { headers: { "X-API-Key": "msh_live_a1b2c3d4e5f6..." } }
);
const { data, pagination } = await res.json();

for (const article of data) {
  console.log(article.published_at, article.headline);
}
import requests

res = requests.get(
    "https://markets.sh/api/v3/news",
    params={"q": "nvidia", "min_quality": 6, "limit": 5},
    headers={"X-API-Key": "msh_live_a1b2c3d4e5f6..."},
)

for article in res.json()["data"]:
    print(article["published_at"], article["headline"])