Real-Time Streams (Orders & Portfolio)
The Streaming API provides real-time updates using Server-Sent Events (SSE). This lightweight protocol allows you to receive push notifications for order updates without the overhead of WebSockets or constant polling.
Overview
Server-Sent Events (SSE) is a standard allowing servers to push data to web clients over HTTP. Our implementation provides:
- Real-time order status updates
- Automatic reconnection handling
- Minimal bandwidth usage
- Simple client implementation
Streams Overview
- Orders Stream: Real-time order updates. See Order Stream
- Portfolio Stream: Real-time portfolio snapshots. See Portfolio Stream
Order Stream
Subscribe to real-time order updates for your entity. Updates are pushed immediately when order status changes occur.
Endpoint
GET https://api.paperinvest.io/v1/stream/orders
Query Parameters
Parameter | Type | Description | Required |
---|---|---|---|
accountId | String | Filter updates for specific account (optional) | No |
Connection Examples
JavaScript
// Browser/Node.js example
const eventSource = new EventSource(
'https://api.paperinvest.io/v1/stream/orders?accountId=550e8400-e29b-41d4-a716-446655440000',
{
headers: {
'Authorization': 'Bearer YOUR_JWT_TOKEN'
}
}
);
// Handle order updates
eventSource.addEventListener('order-update', (event) => {
const order = JSON.parse(event.data);
console.log('Order update:', order);
updateOrderUI(order);
});
// Handle heartbeat
eventSource.addEventListener('heartbeat', (event) => {
console.log('Connection alive');
});
// Handle errors
eventSource.addEventListener('error', (event) => {
if (event.data) {
const error = JSON.parse(event.data);
console.error('Stream error:', error.message);
}
});
// Handle connection errors
eventSource.onerror = (error) => {
console.error('Connection error:', error);
// EventSource will automatically reconnect
};
// Clean up when done
// eventSource.close();
Event Types
The stream emits different event types to handle various scenarios:
Event Type | Description | Frequency |
---|---|---|
order-update | Order status change or fill update | On order changes |
heartbeat | Keep-alive signal | Every 30 seconds |
error | Stream error notification | On errors |
Order Update Payload
Each order-update
event contains essential order information. For complete order details, use the Get Order endpoint.
Order Update Event
{
"orderId": "123e4567-e89b-12d3-a456-426614174000",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"portfolioId": "8b72f1a5-c2e4-48d9-b5a3-1c7e5d3f9e8a",
"brokerId": "default",
"symbol": "AAPL",
"side": "BUY",
"status": "FILLED",
"quantity": 100,
"filledQuantity": 100,
"outstandingQuantity": 0,
"limitPrice": 150.00,
"createdAt": "2025-01-15T10:30:00Z"
}
Event Stream Format
SSE events follow a specific text-based format. Each event consists of fields separated by newlines:
Field | Description | Example |
---|---|---|
id | Unique identifier for the event (optional) | 123e4567-e89b-12d3-a456-426614174000 |
event | Event type name | order-update, heartbeat, error |
data | JSON payload containing event details | {"orderId":"...","status":"FILLED"...} |
retry | Reconnection time in milliseconds (optional) | 5000 |
Example Event Stream
SSE Event Stream
id: 123e4567-e89b-12d3-a456-426614174000
event: order-update
data: {"orderId":"123e4567-e89b-12d3-a456-426614174000","symbol":"AAPL","status":"FILLED","filledQuantity":100,"outstandingQuantity":0}
event: heartbeat
data: {"timestamp":"2025-01-15T10:30:30Z"}
id: 456e7890-e89b-12d3-a456-426614174111
event: order-update
data: {"orderId":"456e7890-e89b-12d3-a456-426614174111","symbol":"MSFT","status":"ACTIVE","filledQuantity":0,"outstandingQuantity":50}
Connection Management
Authentication
The streaming endpoint uses the same JWT token authentication as other API endpoints. Include your bearer token in the Authorization header.
Reconnection
SSE connections automatically reconnect on network interruptions. The EventSource API handles this transparently, using the last event ID to resume where it left off.
Connection Details
- Heartbeat interval: 30 seconds
- Automatic deduplication: Updates within 100ms are deduplicated
Best Practices
- Implement Error Handling: Always handle connection errors and stream errors separately
- Monitor Heartbeats: Use heartbeats to detect stale connections
- Efficient Updates: Process updates efficiently to avoid blocking the event loop
- Resource Cleanup: Close connections when no longer needed
Connection Management
class OrderStreamManager {
constructor(token, accountId) {
this.token = token;
this.accountId = accountId;
this.eventSource = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
const url = new URL('https://api.paperinvest.io/v1/stream/orders');
if (this.accountId) {
url.searchParams.append('accountId', this.accountId);
}
this.eventSource = new EventSource(url, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
this.eventSource.addEventListener('order-update', (event) => {
this.handleOrderUpdate(JSON.parse(event.data));
});
this.eventSource.addEventListener('heartbeat', () => {
this.lastHeartbeat = Date.now();
});
this.eventSource.onerror = (error) => {
console.error('SSE Error:', error);
this.handleReconnect();
};
// Monitor heartbeats
this.heartbeatInterval = setInterval(() => {
if (Date.now() - this.lastHeartbeat > 60000) {
console.warn('No heartbeat for 60s, reconnecting...');
this.reconnect();
}
}, 10000);
}
handleOrderUpdate(order) {
// Process order update
console.log('Order update:', order);
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => this.connect(), 1000 * this.reconnectAttempts);
} else {
console.error('Max reconnection attempts reached');
}
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
clearInterval(this.heartbeatInterval);
}
}
}
// Usage
const streamManager = new OrderStreamManager(
'YOUR_JWT_TOKEN',
'550e8400-e29b-41d4-a716-446655440000'
);
streamManager.connect();
Portfolio Stream
Real-time portfolio valuation snapshots using Server-Sent Events (SSE).
Endpoint
GET https://api.paperinvest.io/v1/stream/portfolio/{portfolioId}
Path parameter:
portfolioId
(string) — Required. The portfolio UUID to stream.
Query Parameters
Parameter | Type | Description | Required |
---|---|---|---|
hz | Number | Update frequency in messages per second (1–10). Default: 2 | No |
Notes:
- Authentication is required via
Authorization: Bearer <JWT>
.
Connection Examples
JavaScript
// Browser/Node.js example
const portfolioId = 'YOUR_PORTFOLIO_ID';
const hz = 2; // messages per second
const url = new URL(`https://api.paperinvest.io/v1/stream/portfolio/${portfolioId}`);
url.searchParams.set('hz', String(hz));
const es = new EventSource(url, {
headers: {
Authorization: `Bearer ${YOUR_JWT}`,
},
});
es.onmessage = (evt) => {
const snapshot = JSON.parse(evt.data);
console.log('Portfolio snapshot:', snapshot);
};
es.addEventListener('ping', (evt) => {
// Heartbeat
});
es.onerror = (err) => {
console.error('SSE error', err);
};
// When done
// es.close();
Snapshot Payload
Portfolio Snapshot
{
"portfolioId": "6b584c9b-f942-4dea-9684-2af93b428577",
"entityId": "b286b122-7bdc-4806-993f-65226672546f",
"timestamp": "2025-09-17T01:32:30.212Z",
"settledCash": 107224.37,
"reservedCash": 0,
"marginLoan": 0,
"longMarketValue": 42282.50,
"shortMarketValue": 0,
"totalEquity": 149506.87,
"unrealizedPnlTotal": 462.95,
"unrealizedPnlPercent": 1.11,
"buyingPower": { "overnight": 299013.74, "dayTrade": 598027.48 },
"symbols": ["TSLA"],
"positions": [
{
"symbol": "TSLA",
"quantity": 100,
"costBasis": 41819.55,
"price": 422.825,
"bid": 422.70,
"ask": 422.95,
"marketValue": 42282.50,
"entryPrice": 418.1955,
"unrealizedPnl": 462.95
}
]
}
Best Practices
- Handle heartbeats (
event: ping
) to keep connections healthy. - Exponential backoff for reconnects on network errors.
- If you multiplex many portfolios, prefer one SSE per portfolio rather than very high
hz
on a single stream.
See Also
- Orders API - Complete order management endpoints
- Authentication - How to obtain JWT tokens
- Rate Limiting - API usage limits