WebSocket Stream
Open one WebSocket connection with your filters to receive new matching articles. You can also send control messages over the same connection.
Articles arrive when they become available in APITube. They may have been published earlier. See Delivery Order and Freshness for details.
What is WebSocket Stream?
WebSocket lets a client and server send messages over one open connection. This stream sends articles as JSON messages with a type field. You can send pings to check the connection. SSE Stream provides a one-way HTTP alternative.
Use cases:
- Live news feeds and trading/monitoring dashboards
- Real-time monitoring for specific topics, entities, or sentiment
- Breaking-news alerts
- Applications that process incoming articles
Benefits:
- Receive articles as soon as they become available
- Every message is a JSON object. Its
typefield tells you whether it contains an article, connection update or error. - The server confirms that the connection and your filters have been accepted
- Check the connection with client
ping, serverpongand heartbeat messages - All
/everythingfilters supported — language, category, sentiment, entities, and more - Field selection with the
flparameter to keep responses smaller - Use
takeoverto replace an old connection after a restart without replacing your API key
Endpoint
GET wss://api.apitube.io/v1/news/wsOne connection = one set of filters. To subscribe to different filter sets, open separate connections.
Authentication
Pass your API key in one of two ways:
- Header (recommended):
X-API-Key: YOUR_API_KEY - Query parameter:
?api_key=YOUR_API_KEY
Without a valid API key the connection is closed with WebSocket close code 1008 (Unauthorized). A scope-restricted key must include the websocket scope.
Quick Start
wscat (command line)
npx wscat -c "wss://api.apitube.io/v1/news/ws?language.code=en&api_key=YOUR_API_KEY"You first receive connected and subscribed messages. New matching articles then arrive in article messages.
JavaScript (Browser)
const ws = new WebSocket(
'wss://api.apitube.io/v1/news/ws?api_key=YOUR_API_KEY&language.code=en'
);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'error') {
console.error(msg.code, msg.message);
return;
}
if (msg.type === 'article') {
const article = msg.data;
console.log(article.title, article.source?.domain);
}
};
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
// Reconnect with exponential backoff (see below)
};Node.js
import WebSocket from 'ws';
const ws = new WebSocket('wss://api.apitube.io/v1/news/ws?language.code=en', {
headers: { 'X-API-Key': 'YOUR_API_KEY' }
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.type === 'error') {
console.error(msg.code, msg.message);
return;
}
if (msg.type === 'article') {
console.log(msg.data.title);
}
});
ws.on('close', (code, reason) => {
console.log('Closed:', code, reason.toString());
});Python
import json
import websocket
def on_message(ws, message):
msg = json.loads(message)
if msg.get("type") == "error":
print(f"Error: {msg['code']} - {msg['message']}")
return
if msg.get("type") == "article":
article = msg["data"]
print(f"{article['title']} — {article.get('source', {}).get('domain')}")
ws = websocket.WebSocketApp(
"wss://api.apitube.io/v1/news/ws?language.code=en",
header={"X-API-Key": "YOUR_API_KEY"},
on_message=on_message,
)
ws.run_forever()Message Format
Every message is a JSON object. Read its type field to decide how to handle it.
Server → Client
After a successful connection, the server sends two confirmation messages:
{"type":"connected","session_id":"req-a1b2c3","timestamp":1718800000000}
{"type":"subscribed","filters":{"language.code":"en"},"timestamp":1718800000000}connected— the connection is established and authenticated.session_ididentifies this session.subscribed— your subscription is accepted.filtersechoes the query parameters you connected with (your API key is not echoed back).
New matching articles then arrive in article messages:
{"type":"article","data":{"id":123456,"title":"...","source":{"domain":"reuters.com"},"published_at":"2026-07-03T...","language":{"code":"en"}}}data— the full article JSON, in the same format as the /everything endpoint (or only the fields you requested withfl).
Control messages:
| Message | When |
|---|---|
{"type":"heartbeat","timestamp":...} | Keep-alive, sent every 30 seconds |
{"type":"pong","timestamp":...} | Reply to a client ping |
{"type":"error","code":"...","message":"..."} | An error occurred (balance, limits, key revoked); the connection then closes |
Client → Server
Send a ping message to confirm that your application is still connected. The server replies with pong:
{ "action": "ping" }Any message from the client confirms that it is still connected. The server replies only to ping; it ignores other message content. Filters cannot change while the connection is open.
Filtering
All filters available on the /everything endpoint work with WebSocket Stream, passed as query parameters at connection time:
By language:
wss://api.apitube.io/v1/news/ws?language.code=en&api_key=YOUR_API_KEYBy category:
wss://api.apitube.io/v1/news/ws?category.id=medtop:13000000&api_key=YOUR_API_KEYBy source:
wss://api.apitube.io/v1/news/ws?source.domain=theverge.com&api_key=YOUR_API_KEYMultiple filters:
wss://api.apitube.io/v1/news/ws?language.code=en&category.id=medtop:13000000&sentiment.overall.polarity=positive&api_key=YOUR_API_KEYIn plain language:
wss://api.apitube.io/v1/news/ws?prompt=breaking technology news in English&api_key=YOUR_API_KEYThe API converts prompt into filters before accepting the subscription. The subscribed message shows those filters, so you can check how the prompt was understood:
{"type":"subscribed","filters":{"organization.name":"Tesla","language.code":"en"},"timestamp":1718800000000}If a prompt cannot be used, the API sends an error message and closes the connection with code 4001:
{"type":"error","code":"ER0800","message":"prompt must be a string between 3 and 500 characters."}A new prompt translation costs 2 points once per connection. If the translation is already cached, it is free. prompt requires Basic or above. On Free and Starter, the socket closes with ER0706.
For the full list of available filters, see Parameters.
Field Selection
Use fl to receive only the fields you need and keep responses smaller. The data object in each article message contains only those fields:
wss://api.apitube.io/v1/news/ws?language.code=en&fl=id,title,source.domain&api_key=YOUR_API_KEYDelivery Order and Freshness
Articles arrive in the order they become available in APITube. This may differ from their published_at order.
published_atis the original publication time when available. Otherwise, it shows when the article became available in APITube.- Delivery time varies by article and publisher. A newly delivered article may have an older
published_atvalue.
Use published_at.start to receive only recent articles. Older articles are excluded before delivery and billing. Use source.rank.opr.min (Open PageRank) to select higher-ranked sources. These filters do not change delivery order. See Parameters.
Keep-Alive
The stream uses two kinds of connection checks:
- Server heartbeat — every 30 seconds the server sends a
{"type":"heartbeat"}message and a protocol-level WebSocket ping. Standard clients (the browserWebSocketAPI and the Nodewslibrary) reply to the protocol ping automatically. - Client ping — you may send
{"action":"ping"}at any time and the server replies with{"type":"pong"}.
If a client stops replying to pings, the API closes its connection after one or two heartbeat intervals. This frees the session even if no matching articles are arriving.
Reconnection
A new WebSocket connection starts with the newest matching articles. It does not replay articles missed while disconnected and has no Last-Event-ID equivalent. Use SSE Stream if you need automatic resume. For WebSocket, reconnect with increasing delays between attempts:
let reconnectDelay = 1000;
function connect() {
const ws = new WebSocket('wss://api.apitube.io/v1/news/ws?language.code=en&api_key=YOUR_API_KEY');
ws.onopen = () => {
reconnectDelay = 1000; // reset on success
};
ws.onclose = () => {
setTimeout(connect, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'article') console.log(msg.data.title);
};
}
connect();To recover articles missed during a disconnection, request that time range from /everything, then reconnect to the stream.
Parameters
| Parameter | Description |
|---|---|
api_key | API key (alternative to X-API-Key header) |
takeover | true to evict your oldest connection instead of being rejected at the connection limit |
fl | Field selection (e.g., id,title,source.domain) |
prompt | Describe filters in plain language (3–500 characters). The subscribed message shows the selected filters. Requires Basic or above. |
All /everything filters | language.code, category.id, source.domain, sentiment.overall.polarity, title, organization.name, and more |
Technical Details
| Property | Value |
|---|---|
| Protocol | WebSocket (wss://), sends messages in both directions |
| Poll interval | Every 10 seconds |
| Heartbeat interval | Every 30 seconds |
| Articles per poll cycle | Up to 50 |
| Max message size | 128 KB |
| Max connection lifetime | 6 hours (then reconnect) |
| Billing | 1 credit per delivered article |
| Reconnection | Manual (exponential backoff) |
Concurrent Connections
Your plan limits how many WebSocket connections you can keep open at the same time:
| Plan | Max concurrent WebSocket connections |
|---|---|
| Free | 1 |
| Starter | 2 |
| Basic | 2 |
| Professional | 10 |
| Corporate | 50 |
On Free, you can open 1 WebSocket connection with 1,000 deliveries per day. Article text is a preview; links, images, descriptions and other fields are full. Articles arrive with a 12-hour delay. Paid plans include full text and more connections: Starter 2, Basic 2, Professional 10 and Corporate 50.
Opening more connections than your plan allows sends an error message with code ER0403 and closes the connection with code 4003:
{"type":"error","code":"ER0403","message":"Maximum WebSocket connections (10) reached for your plan."}Takeover
Connect with ?takeover=true to replace your oldest connection when all slots are in use. This frees a slot after a restart without replacing your API key. The old client receives an error message with ER0365, and its connection closes with code 4007:
wss://api.apitube.io/v1/news/ws?language.code=en&takeover=true&api_key=YOUR_API_KEYUse takeover if your application stopped without closing its old connection. Replacing the API key also closes all sessions opened with that key.
Billing
Each article delivered through the stream costs 1 credit. Credits are deducted in real time as articles arrive — never for the connection itself or for heartbeats.
How it works:
- When you connect, the server checks your balance. With no WebSocket credits and no paid balance, it sends an
errormessage with codeER0404and closes the connection with code4002. - While the stream is active, 1 credit is deducted per article sent to you — only after the article is confirmed delivered to an open socket.
- If your balance runs out mid-stream, the server sends an
errormessage with codeER0402("WebSocket points exhausted") and closes the connection.
Credit source priority:
- Plan WebSocket credits (
ws_points) are used first. - If those are exhausted and you have a positive paid balance, the paid balance is used instead.
Each connection is billed separately. If an article matches several of your streams, you pay once per delivery. Use one stream where possible, or choose filters that do not overlap.
Error Handling
The API sends errors in an error message, then closes the connection:
{"type":"error","code":"ER0402","message":"WebSocket points exhausted."}| Error code | Meaning | Close code |
|---|---|---|
ER0401 | Invalid filters | 4001 |
ER0402 | WebSocket credits exhausted mid-stream | 4002 |
ER0403 | Maximum WebSocket connections reached | 4003 |
ER0404 | No WebSocket credits at connection time | 4002 |
ER0361 | API key revoked | 4004 |
ER0362 | Connection lifetime (6h) exceeded — reconnect | 4005 |
ER0365 | Connection replaced by a newer one (takeover) | 4007 |
ER0177 | Monthly pay-as-you-go spending limit reached | 4006 |
WebSocket close codes at a glance:
| Close code | Reason |
|---|---|
1000 | Normal close |
1008 | Unauthorized (invalid or missing API key) |
4001 | Invalid filters |
4002 | Balance exhausted / no credits |
4003 | Connection limit reached |
4004 | API key revoked |
4005 | Lifetime exceeded |
4006 | Pay-as-you-go spending limit reached |
4007 | Replaced by a new connection using takeover |
SSE or WebSocket?
Both deliver the same articles in real time with the same filters and per-article billing. Choose by connection style:
| SSE Stream | WebSocket Stream | |
|---|---|---|
| Protocol | HTTP (one-way) | WebSocket (two-way) |
| Message format | event: article + data: | {"type":"article","data":{...}} |
| Reconnection | Automatic via Last-Event-ID | Manual (exponential backoff) |
| Slot recovery | Session list/terminate endpoints | takeover option |
| Best for | Simple one-way reading, browser EventSource | Apps that send and receive messages, with ping/pong checks |
Choose SSE for a one-way feed with automatic resume. Choose WebSocket to receive JSON messages and send control messages on the same connection. To receive articles at your own URL, use Webhooks.
Next Steps
- Learn about all available Parameters
- Compare with the SSE Stream and Webhooks
- Check Authentication options
- See HTTP Response Codes for error handling
Support
If you encounter issues:
- Check the Technical FAQ
- Verify your API key on apitube.io
- Contact support through the APITube website