Deduplicated, quality-scored, entity-tagged financial news, translated within seconds of publication. Streamed over WebSocket, queryable over REST, one API key for both.
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.
wss://news.markets.sh/ws/news?api_key=msh_live_…&topic=all&replay=100
| Param | Type | Description |
|---|---|---|
api_key | string | Your markets.sh API key (required) |
topic | string | all, tag:{name}, publisher:{domain}, company:{id} |
min_quality | int 1-10 | Minimum quality score |
tickers | csv | e.g. BTC,ETH |
sentiment | string | positive / negative / neutral / mixed |
countries | csv | ISO country codes |
primary_category | csv | e.g. markets,policy |
asset_classes | csv | e.g. crypto,equities |
content_type | csv | Content type filter |
replay | int | Replay up to this many recent matching articles on connect (max 200) |
{
"type": "article",
"data": { "id": "...", "headline": "...", ... }
}
{
"type": "heartbeat",
"ts": "2026-03-16T21:30:00Z"
}
// Update filters mid-stream
{
"type": "filter",
"data": { "min_quality": 7, "primary_category": ["markets"] }
}
// Keepalive ping
{ "type": "ping" }
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"]}}
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.
https://markets.sh/api/v3/news?q=nvidia&min_quality=6&limit=5
| Param | Type | Description |
|---|---|---|
q | string | Keyword query across headline, summary, and body |
symbols | csv | Tickers resolved to company ids, e.g. NVDA,ASML |
tickers | csv | Direct ticker filter (no resolution) |
tags | csv | e.g. bitcoin,ecb |
primary_category | csv | e.g. markets,policy |
asset_classes | csv | e.g. crypto,equities |
countries | csv | ISO country codes |
sentiment | string | positive / negative / neutral / mixed |
min_quality | int 1-10 | Minimum quality score |
sources / exclude_sources | csv | Publisher domains to include or exclude |
language | csv | ISO language codes |
from / to | ISO 8601 | Published-at range |
fields | csv | Return only these fields; add body to include article bodies |
limit / offset | int | Default 15 per page; offset + limit capped at 1000 |
{
"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
}
}
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"])