OptiTrade Notification API - Frontend Developer
Guide
Overview
This guide provides everything a frontend developer needs to integrate Opti-
Trade’s notification system. All endpoints require JWT authentication and
return JSON responses.
Authentication
Required Header:
headers: {
'Authorization': 'Bearer YOUR_JWT_TOKEN',
'Content-Type': 'application/json'
}
Base URL
https://your-domain.com/notifications
User Notification Preferences
Get User Preferences
Endpoint: GET /preferences/{user_id}
What it does: Retrieves user’s notification settings. Creates default settings
if none exist.
Frontend Use Case: Load user preferences for settings page
Request:
fetch('/notifications/preferences/123', {
headers: { 'Authorization': 'Bearer token' }
})
Response:
{
"user_id": 123,
"price_alert_threshold": 5.0,
"portfolio_alert_threshold": 1000.0,
"email_enabled": true,
"push_enabled": true,
"quiet_hours_start": 22,
"quiet_hours_end": 6
}
1
Response Fields Explained: - price_alert_threshold: Percentage change
(5.0 = 5%) that triggers watchlist alerts - portfolio_alert_threshold: Dollar
amount ($1000) that triggers portfolio value alerts - email_enabled: Whether
user receives email notifications - push_enabled: Whether user receives push
notifications (future feature) - quiet_hours_start: Hour (0-23) when notifica-
tions stop (22 = 10 PM) - quiet_hours_end: Hour (0-23) when notifications
resume (6 = 6 AM)
Frontend Implementation:
// Settings form component
const [preferences, setPreferences] = useState({
price_alert_threshold: 5.0,
portfolio_alert_threshold: 1000.0,
email_enabled: true,
push_enabled: true,
quiet_hours_start: 22,
quiet_hours_end: 6
});
// Load preferences on component mount
useEffect(() => {
loadUserPreferences();
}, []);
const loadUserPreferences = async () => {
const response = await fetch(`/notifications/preferences/${userId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
setPreferences(data);
};
Update User Preferences
Endpoint: PUT /preferences/{user_id}
What it does: Updates user’s notification settings. Only send fields you want
to change.
Frontend Use Case: Save settings form changes
Request Body (all fields optional):
{
"price_alert_threshold": 2.5,
"portfolio_alert_threshold": 500.0,
"email_enabled": false,
"push_enabled": true,
2
"quiet_hours_start": 23,
"quiet_hours_end": 7
}
Field Validation: - price_alert_threshold: 0.1 to 100.0 (percentage) -
portfolio_alert_threshold: 1.0 to 1000000.0 (dollars) - quiet_hours_start/end:
0 to 23 (24-hour format)
Response: Same as GET preferences
Frontend Implementation:
const updatePreferences = async (changes) => {
try {
const response = await fetch(`/notifications/preferences/${userId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(changes)
});
if (response.ok) {
const updated = await response.json();
setPreferences(updated);
showSuccessMessage('Preferences updated successfully');
}
} catch (error) {
showErrorMessage('Failed to update preferences');
}
};
// Usage in form
const handleSubmit = (formData) => {
updatePreferences({
price_alert_threshold: parseFloat(formData.priceThreshold),
email_enabled: formData.emailEnabled
});
};
Price Alerts Management
Create Price Alert
Endpoint: POST /price-alerts/{user_id}
What it does: Creates a new price alert for a stock symbol
3
Frontend Use Case: “Set Price Alert” button on stock pages
Request Body:
{
"symbol": "AAPL",
"target_price": 150.00,
"condition": "above"
}
Field Requirements: - symbol: Stock symbol (uppercase, 1-7 characters) -
target_price: Price threshold (positive number, 2 decimal places) - condition:
Either “above” or “below”
Response:
{
"id": 456,
"user_id": 123,
"symbol": "AAPL",
"target_price": 150.00,
"condition": "above",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"triggered_at": null
}
Response Fields: - id: Unique alert ID (use for deletion) - is_active: true =
monitoring, false = triggered or disabled - triggered_at: null if not triggered,
timestamp if triggered
Error Responses: - 400: Invalid condition or negative price - 409: Alert
already exists for this combination
Frontend Implementation:
const createPriceAlert = async (symbol, targetPrice, condition) => {
try {
const response = await fetch(`/notifications/price-alerts/${userId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
symbol: symbol.toUpperCase(),
target_price: parseFloat(targetPrice),
condition: condition
})
});
4
if (response.status === 409) {
showErrorMessage('Price alert already exists for this stock and price');
return;
}
if (response.ok) {
const alert = await response.json();
showSuccessMessage(`Price alert created for ${symbol} at $${targetPrice}`);
refreshAlertsList();
}
} catch (error) {
showErrorMessage('Failed to create price alert');
}
};
// Form validation
const validatePriceAlert = (symbol, price, condition) => {
if (!symbol || symbol.length > 7) return 'Invalid symbol';
if (price <= 0) return 'Price must be positive';
if (!['above', 'below'].includes(condition)) return 'Invalid condition';
return null;
};
Get User’s Price Alerts
Endpoint: GET /price-alerts/{user_id}?active_only=true
What it does: Returns list of user’s price alerts
Frontend Use Case: Display alerts in user dashboard
Query Parameters: - active_only: true (default) = only active alerts, false
= all alerts
Response:
[
{
"id": 456,
"user_id": 123,
"symbol": "AAPL",
"target_price": 150.00,
"condition": "above",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"triggered_at": null
},
5
{
"id": 457,
"user_id": 123,
"symbol": "TSLA",
"target_price": 200.00,
"condition": "below",
"is_active": false,
"created_at": "2024-01-14T15:20:00Z",
"triggered_at": "2024-01-15T09:45:00Z"
}
]
Frontend Implementation:
const [alerts, setAlerts] = useState([]);
const [showTriggered, setShowTriggered] = useState(false);
const loadAlerts = async () => {
const response = await fetch(
`/notifications/price-alerts/${userId}?active_only=${!showTriggered}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const data = await response.json();
setAlerts(data);
};
// Alert list component
const AlertsList = () => (
<div>
<button onClick={() => setShowTriggered(!showTriggered)}>
{showTriggered ? 'Show Active Only' : 'Show All Alerts'}
</button>
{alerts.map(alert => (
<div key={alert.id} className={alert.is_active ? 'active' : 'triggered'}>
<span>{alert.symbol}</span>
<span>${alert.target_price}</span>
<span>{alert.condition}</span>
<span>{alert.is_active ? 'Active' : 'Triggered'}</span>
{alert.is_active && (
<button onClick={() => deleteAlert(alert.id)}>Delete</button>
)}
</div>
))}
</div>
);
6
Delete Price Alert
Endpoint: DELETE /price-alerts/{alert_id}
What it does: Removes a price alert
Frontend Use Case: Delete button in alerts list
Response:
{
"message": "Price alert deleted successfully"
}
Frontend Implementation:
const deleteAlert = async (alertId) => {
if (!confirm('Delete this price alert?')) return;
try {
const response = await fetch(`/notifications/price-alerts/${alertId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
setAlerts(alerts.filter(alert => alert.id !== alertId));
showSuccessMessage('Price alert deleted');
}
} catch (error) {
showErrorMessage('Failed to delete alert');
}
};
Notification History
Get Notification History
Endpoint: GET /history/{user_id}?limit=50&offset=0&event_type=price_alert
What it does: Returns user’s notification history with pagination
Frontend Use Case: Notification center, history page
Query Parameters: - limit: Number of notifications (1-100, default: 50) -
offset: Skip notifications for pagination (default: 0) - event_type: Filter by
type (optional)
Event Types: - price_alert: Price threshold notifications - transaction_complete:
Buy/sell confirmations - portfolio_change: Portfolio value changes - test:
Test notifications
7
Response:
[
{
"id": 789,
"event_type": "price_alert",
"title": "Price Alert: AAPL",
"message": "AAPL has reached $150.25 (target: $150.00)",
"created_at": "2024-01-15T10:30:00Z",
"status": "delivered"
},
{
"id": 790,
"event_type": "transaction_complete",
"title": "Transaction Complete",
"message": "Successfully bought 100 shares of TSLA at $195.50",
"created_at": "2024-01-15T09:15:00Z",
"status": "delivered"
}
]
Status Values: - delivered: Successfully sent - pending: Waiting to be sent
- failed: Delivery failed
Frontend Implementation:
const [notifications, setNotifications] = useState([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const loadNotifications = async (offset = 0, eventType = null) => {
setLoading(true);
let url = `/notifications/history/${userId}?limit=20&offset=${offset}`;
if (eventType) url += `&event_type=${eventType}`;
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
if (offset === 0) {
setNotifications(data);
} else {
setNotifications(prev => [...prev, ...data]);
}
8
setHasMore(data.length === 20);
setLoading(false);
};
// Infinite scroll implementation
const loadMore = () => {
if (!loading && hasMore) {
loadNotifications(notifications.length);
}
};
// Filter by event type
const filterByType = (eventType) => {
setNotifications([]);
loadNotifications(0, eventType);
};
Testing & Utilities
Send Test Notification
Endpoint: POST /test/{user_id}
What it does: Sends a test notification to verify user’s settings
Frontend Use Case: “Test Notifications” button in settings
Response:
{
"message": "Test notification queued successfully"
}
Frontend Implementation:
const sendTestNotification = async () => {
try {
const response = await fetch(`/notifications/test/${userId}`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
showSuccessMessage('Test notification sent! Check your email.');
}
} catch (error) {
showErrorMessage('Failed to send test notification');
}
};
9
Get Notification Statistics
Endpoint: GET /stats/{user_id}
What it does: Returns user’s notification statistics
Frontend Use Case: Dashboard metrics, settings page info
Response:
{
"total_notifications": 150,
"delivered_notifications": 147,
"pending_notifications": 2,
"active_price_alerts": 5,
"delivery_rate": 98.0
}
Response Fields: - total_notifications: All notifications ever sent
to user - delivered_notifications: Successfully delivered count -
pending_notifications: Currently waiting to be sent - active_price_alerts:
Number of active price alerts - delivery_rate: Percentage of successful deliv-
eries
Frontend Implementation:
const [stats, setStats] = useState(null);
const loadStats = async () => {
const response = await fetch(`/notifications/stats/${userId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
setStats(data);
};
// Stats display component
const NotificationStats = () => (
<div className="stats-grid">
<div className="stat-card">
<h3>Total Notifications</h3>
<span>{stats?.total_notifications || 0}</span>
</div>
<div className="stat-card">
<h3>Delivery Rate</h3>
<span>{stats?.delivery_rate || 0}%</span>
</div>
<div className="stat-card">
<h3>Active Alerts</h3>
10
<span>{stats?.active_price_alerts || 0}</span>
</div>
<div className="stat-card">
<h3>Pending</h3>
<span>{stats?.pending_notifications || 0}</span>
</div>
</div>
);
WebSocket Real-Time Notifications
Connection Setup
Endpoint: WS /ws/{user_id}
What it does: Establishes real-time connection for instant notifications
Frontend Use Case: Live notifications, real-time price updates
Connection Example:
class NotificationWebSocket {
constructor(userId, token) {
this.userId = userId;
this.token = token;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
// Note: WebSocket doesn't support custom headers, so token in URL
this.ws = new WebSocket(`ws://your-domain.com/ws/${this.userId}`);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.sendPing();
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.reconnect();
11
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
handleMessage(data) {
switch (data.type) {
case 'notification':
this.showNotification(data);
break;
case 'price_update':
this.updatePrices(data.data);
break;
case 'pong':
// Heartbeat response
break;
}
}
showNotification(notification) {
// Display notification in UI
const toast = {
id: notification.id,
title: notification.title,
message: notification.message,
type: notification.event_type,
timestamp: notification.created_at
};
// Add to notification center
addToNotificationCenter(toast);
// Show toast notification
showToast(toast);
// Play notification sound
if (notification.priority <= 2) {
playNotificationSound();
}
}
updatePrices(priceData) {
// Update price displays in UI
priceData.forEach(item => {
12
updateStockPrice(item.symbol, item.price);
});
}
sendPing() {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
setTimeout(() => this.sendPing(), 30000); // Ping every 30 seconds
}
}
subscribeToPrices() {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'subscribe_prices' }));
}
}
reconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.pow(2, this.reconnectAttempts) * 1000; // Exponential backoff
setTimeout(() => this.connect(), delay);
}
}
disconnect() {
if (this.ws) {
this.ws.close();
}
}
}
// Usage in React component
const useNotificationWebSocket = (userId, token) => {
const [ws, setWs] = useState(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
const websocket = new NotificationWebSocket(userId, token);
websocket.connect();
setWs(websocket);
return () => {
websocket.disconnect();
};
}, [userId, token]);
13
return { ws, connected };
};
WebSocket Message Types
Incoming Messages Notification Message:
{
"type": "notification",
"id": 456,
"event_type": "price_alert",
"title": "Price Alert: AAPL",
"message": "AAPL has reached $150.25",
"priority": 2,
"data": {
"symbol": "AAPL",
"current_price": 150.25,
"target_price": 150.00,
"condition": "above"
},
"created_at": "2024-01-15T10:30:00Z"
}
Price Update Message:
{
"type": "price_update",
"data": [
{
"symbol": "AAPL",
"price": 150.25,
"time_fetched": "2024-01-15 10:30:00"
}
],
"timestamp": "2024-01-15T10:30:00Z"
}
Outgoing Messages Ping (Heartbeat):
{
"type": "ping"
}
Subscribe to Price Updates:
{
"type": "subscribe_prices"
}
14
Error Handling
Standard Error Response
{
"detail": "Error description",
"error_code": "SPECIFIC_ERROR_CODE",
"timestamp": "2024-01-15T10:30:00Z"
}
Common HTTP Status Codes
• 200 OK: Request successful
• 201 Created: Resource created
• 400 Bad Request: Invalid request data
• 401 Unauthorized: Missing or invalid token
• 404 Not Found: Resource not found
• 409 Conflict: Resource already exists
• 422 Unprocessable Entity: Validation error
• 429 Too Many Requests: Rate limit exceeded
• 500 Internal Server Error: Server error
Frontend Error Handling
const handleApiError = async (response) => {
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
// Redirect to login
redirectToLogin();
break;
case 409:
showErrorMessage('This alert already exists');
break;
case 429:
showErrorMessage('Too many requests. Please wait a moment.');
break;
default:
showErrorMessage(error.detail || 'An error occurred');
}
throw new Error(error.detail);
}
return response.json();
15
};
// Usage
const createAlert = async (data) => {
try {
const response = await fetch('/notifications/price-alerts/123', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await handleApiError(response);
return result;
} catch (error) {
console.error('Failed to create alert:', error);
}
};
Rate Limits
Per User Limits: - Notification creation: 100 requests/hour - Price alerts:
50 active alerts maximum - WebSocket connections: 5 concurrent connections -
Test notifications: 10 per hour
Frontend Implementation:
// Rate limit tracking
const rateLimiter = {
testNotifications: { count: 0, resetTime: Date.now() + 3600000 },
canSendTest() {
const now = Date.now();
if (now > this.testNotifications.resetTime) {
this.testNotifications = { count: 0, resetTime: now + 3600000 };
}
return this.testNotifications.count < 10;
},
recordTestSent() {
this.testNotifications.count++;
}
};
const sendTestNotification = async () => {
16
if (!rateLimiter.canSendTest()) {
showErrorMessage('Test notification limit reached. Try again in an hour.');
return;
}
// Send test notification
rateLimiter.recordTestSent();
};
Complete Integration Example
// Complete notification system integration
class NotificationManager {
constructor(userId, token) {
this.userId = userId;
this.token = token;
this.ws = null;
this.preferences = null;
}
async initialize() {
// Load user preferences
await this.loadPreferences();
// Connect WebSocket
this.connectWebSocket();
// Load initial data
await this.loadAlerts();
await this.loadNotificationHistory();
}
async loadPreferences() {
const response = await fetch(`/notifications/preferences/${this.userId}`, {
headers: { 'Authorization': `Bearer ${this.token}` }
});
this.preferences = await response.json();
}
connectWebSocket() {
this.ws = new NotificationWebSocket(this.userId, this.token);
this.ws.connect();
}
async createPriceAlert(symbol, targetPrice, condition) {
const response = await fetch(`/notifications/price-alerts/${this.userId}`, {
17
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ symbol, target_price: targetPrice, condition })
});
return handleApiError(response);
}
async updatePreferences(changes) {
const response = await fetch(`/notifications/preferences/${this.userId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(changes)
});
this.preferences = await handleApiError(response);
return this.preferences;
}
}
// Usage in React app
const App = () => {
const [notificationManager, setNotificationManager] = useState(null);
useEffect(() => {
const manager = new NotificationManager(userId, token);
manager.initialize();
setNotificationManager(manager);
return () => {
manager.ws?.disconnect();
};
}, [userId, token]);
return (
<div>
{/* Your app components */ }
</div>
);
};
18
“‘
19