Documentation Index
Fetch the complete documentation index at: https://docs.byul.ai/llms.txt
Use this file to discover all available pages before exploring further.
Important Security Note
Never use API keys directly in React. API keys will be exposed to all website visitors in the browser. Always use a backend proxy.
Recommended Architecture
Insecure Pattern (Don’t do this)
// This exposes your API key to everyone
const apiKey = 'byul_api_key';
fetch('https://api.byul.ai/api/v2/news', {
headers: { 'X-API-Key': apiKey } // Not recommended: Exposed in browser
});
Secure Pattern (Recommended)
// React calls your backend, backend calls Byul API
fetch('/api/news') // Recommended: Your backend handles API key
Backend Proxy Setup
First, create a backend API that proxies requests to Byul:Node.js/Express Backend
// server/server.js
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Proxy endpoint
app.get('/api/news', async (req, res) => {
try {
const { limit, minImportance, symbol, q, startDate, endDate } = req.query;
const params = new URLSearchParams({
limit: limit || '20',
minImportance: minImportance || '6'
});
if (symbol) params.append('symbol', symbol);
if (q) params.append('q', q);
if (startDate) params.append('startDate', startDate);
if (endDate) params.append('endDate', endDate);
const response = await fetch(`https://api.byul.ai/api/v2/news?${params}`, {
headers: { 'X-API-Key': process.env.BYUL_API_KEY }
});
if (!response.ok) {
return res.status(response.status).json({ error: 'API request failed' });
}
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(3001, () => {
console.log('Proxy server running on port 3001');
});
React Examples
Basic News Component
import React, { useState, useEffect } from 'react';
function NewsComponent() {
const [news, setNews] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchNews();
}, []);
const fetchNews = async () => {
try {
setLoading(true);
// Call your backend, not Byul API directly
const response = await fetch('/api/news?limit=10&minImportance=7&startDate=2024-01-01T00:00:00.000Z&endDate=2024-01-31T23:59:59.999Z');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setNews(data.items);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading news...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="news-container">
<h2>Financial News</h2>
{news.map(article => (
<div key={article._id} className="news-item">
<h3>
<a href={article.url} target="_blank" rel="noopener noreferrer">
{article.title}
</a>
</h3>
<p>
Importance: {article.importanceScore}/10 |
Category: {article.category} |
{article.symbols && article.symbols.length > 0 && (
<span> Symbols: {article.symbols.join(', ')}</span>
)}
</p>
<small>{new Date(article.date).toLocaleString()}</small>
</div>
))}
</div>
);
}
export default NewsComponent;
News Hook with Filtering
import { useState, useEffect } from 'react';
function useNews(filters = {}) {
const [news, setNews] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchNews = async (newFilters = {}) => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams({
limit: '20',
minImportance: '6',
...filters,
...newFilters
});
const response = await fetch(`/api/news?${params}`);
if (!response.ok) {
throw new Error(`Failed to fetch news: ${response.status}`);
}
const data = await response.json();
setNews(data.items);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchNews();
}, []);
return {
news,
loading,
error,
refetch: fetchNews
};
}
// Usage
function NewsApp() {
const { news, loading, error, refetch } = useNews();
const [symbol, setSymbol] = useState('');
const handleSymbolFilter = (e) => {
e.preventDefault();
refetch({ symbol });
};
return (
<div>
<form onSubmit={handleSymbolFilter}>
<input
type="text"
placeholder="Filter by symbol (e.g., AAPL)"
value={symbol}
onChange={(e) => setSymbol(e.target.value)}
/>
<button type="submit">Filter</button>
</form>
{loading && <p>Loading...</p>}
{error && <p>Error: {error}</p>}
<div>
{news.map(article => (
<div key={article._id}>
<h3>{article.title}</h3>
<p>Importance: {article.importanceScore}/10</p>
</div>
))}
</div>
</div>
);
}
Real-time News Dashboard
import React, { useState, useEffect, useRef } from 'react';
function NewsDashboard() {
const [articles, setArticles] = useState([]);
const [connected, setConnected] = useState(false);
const [error, setError] = useState(null);
const intervalRef = useRef(null);
useEffect(() => {
startPolling();
return () => stopPolling();
}, []);
const startPolling = () => {
setConnected(true);
// Fetch immediately
fetchLatestNews();
// Then poll every 30 seconds
intervalRef.current = setInterval(fetchLatestNews, 30000);
};
const stopPolling = () => {
setConnected(false);
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
const fetchLatestNews = async () => {
try {
const response = await fetch('/api/news?limit=10&minImportance=7');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Update articles, avoiding duplicates
setArticles(prevArticles => {
const existingIds = new Set(prevArticles.map(a => a._id));
const newArticles = data.items.filter(a => !existingIds.has(a._id));
return [...newArticles, ...prevArticles].slice(0, 50); // Keep latest 50
});
setError(null);
} catch (err) {
setError(err.message);
setConnected(false);
}
};
return (
<div className="dashboard">
<header className="dashboard-header">
<h1>Byul Financial News Dashboard</h1>
<div className={`status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? 'Connected' : 'Disconnected'}
</div>
</header>
{error && (
<div className="error-banner">
Error: {error}
<button onClick={startPolling}>Retry</button>
</div>
)}
<div className="news-grid">
{articles.map(article => (
<div
key={article._id}
className={`news-card importance-${article.importanceScore}`}
>
<div className="news-header">
<span className="importance-badge">
{article.importanceScore}/10
</span>
<span className="category">{article.category}</span>
</div>
<h3>
<a href={article.url} target="_blank" rel="noopener noreferrer">
{article.title}
</a>
</h3>
{article.symbols && article.symbols.length > 0 && (
<div className="symbols">
{article.symbols.map(symbol => (
<span key={symbol} className="symbol-tag">{symbol}</span>
))}
</div>
)}
<div className="news-meta">
<small>{new Date(article.date).toLocaleString()}</small>
{article.sentiment && (
<span className={`sentiment ${article.sentiment}`}>
{article.sentiment}
</span>
)}
</div>
</div>
))}
</div>
{articles.length === 0 && !error && (
<div className="no-news">
{connected ? 'Waiting for news updates...' : 'Not connected'}
</div>
)}
</div>
);
}
export default NewsDashboard;
Portfolio News Tracker
import React, { useState, useEffect } from 'react';
function PortfolioTracker() {
const [portfolio, setPortfolio] = useState(['AAPL', 'GOOGL', 'MSFT']);
const [news, setNews] = useState([]);
const [newSymbol, setNewSymbol] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
if (portfolio.length > 0) {
fetchPortfolioNews();
}
}, [portfolio]);
const fetchPortfolioNews = async () => {
setLoading(true);
try {
// Fetch news for each symbol in parallel
const promises = portfolio.map(symbol =>
fetch(`/api/news?symbol=${symbol}&minImportance=6&limit=20`)
.then(res => res.json())
);
const results = await Promise.all(promises);
// Combine and deduplicate
const allNews = [];
const seenIds = new Set();
results.forEach(data => {
data.items?.forEach(article => {
if (!seenIds.has(article._id)) {
allNews.push(article);
seenIds.add(article._id);
}
});
});
// Sort by importance and date
allNews.sort((a, b) => {
if (b.importanceScore !== a.importanceScore) {
return b.importanceScore - a.importanceScore;
}
return new Date(b.date) - new Date(a.date);
});
setNews(allNews);
} catch (error) {
console.error('Failed to fetch portfolio news:', error);
} finally {
setLoading(false);
}
};
const addSymbol = (e) => {
e.preventDefault();
const symbol = newSymbol.trim().toUpperCase();
if (symbol && !portfolio.includes(symbol)) {
setPortfolio(prev => [...prev, symbol]);
setNewSymbol('');
}
};
const removeSymbol = (symbolToRemove) => {
setPortfolio(prev => prev.filter(s => s !== symbolToRemove));
};
return (
<div className="portfolio-tracker">
<h2>Portfolio News Tracker</h2>
<div className="portfolio-management">
<div className="portfolio-symbols">
<h3>Portfolio ({portfolio.length} symbols)</h3>
<div className="symbols-list">
{portfolio.map(symbol => (
<div key={symbol} className="symbol-item">
<span>{symbol}</span>
<button
onClick={() => removeSymbol(symbol)}
className="remove-btn"
>
×
</button>
</div>
))}
</div>
</div>
<form onSubmit={addSymbol} className="add-symbol-form">
<input
type="text"
placeholder="Add symbol (e.g., TSLA)"
value={newSymbol}
onChange={(e) => setNewSymbol(e.target.value)}
pattern="[A-Z]{1,5}"
title="Enter 1-5 uppercase letters"
/>
<button type="submit">Add Symbol</button>
</form>
</div>
<div className="news-section">
<div className="news-header">
<h3>Portfolio News ({news.length} articles)</h3>
<button onClick={fetchPortfolioNews} disabled={loading}>
{loading ? 'Loading...' : 'Refresh'}
</button>
</div>
{loading && <div className="loading">Loading portfolio news...</div>}
<div className="news-list">
{news.map(article => (
<div key={article._id} className="portfolio-news-item">
<div className="news-symbols">
{article.symbols?.join(', ') || 'General'}
</div>
<h4>
<a href={article.url} target="_blank" rel="noopener noreferrer">
{article.title}
</a>
</h4>
<div className="news-details">
<span className="importance">
Importance: {article.importanceScore}/10
</span>
<span className="category">{article.category}</span>
<span className="date">
{new Date(article.date).toLocaleDateString()}
</span>
</div>
</div>
))}
</div>
</div>
</div>
);
}
export default PortfolioTracker;
Next.js Integration
API Route (Next.js 13+ App Router)
// app/api/news/route.js
import { NextResponse } from 'next/server';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const limit = searchParams.get('limit') || '20';
const minImportance = searchParams.get('minImportance') || '6';
const symbol = searchParams.get('symbol');
const q = searchParams.get('q');
try {
const params = new URLSearchParams({ limit, minImportance });
if (symbol) params.append('symbol', symbol);
if (q) params.append('q', q);
const response = await fetch(`https://api.byul.ai/api/v2/news?${params}`, {
headers: {
'X-API-Key': process.env.BYUL_API_KEY
},
// Next.js caching
next: { revalidate: 300 } // 5 minutes
});
if (!response.ok) {
return NextResponse.json(
{ error: 'Failed to fetch news' },
{ status: response.status }
);
}
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
React Server Component (Next.js 13+)
// app/news/page.js
async function getNews() {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/news`, {
next: { revalidate: 300 } // 5 minute cache
});
if (!response.ok) {
throw new Error('Failed to fetch news');
}
return response.json();
}
export default async function NewsPage() {
const newsData = await getNews();
return (
<div>
<h1>Latest Financial News</h1>
{newsData.items.map(article => (
<div key={article._id} className="news-item">
<h2>{article.title}</h2>
<p>Importance: {article.importanceScore}/10</p>
<a href={article.url} target="_blank" rel="noopener noreferrer">
Read more
</a>
</div>
))}
</div>
);
}
Environment Setup
Create React App
# Install dependencies
npm install
# Create .env.local file
echo "REACT_APP_API_URL=http://localhost:3001" > .env.local
# Start development server
npm start
Next.js
# Create .env.local file
echo "BYUL_API_KEY=byul_api_key" > .env.local
echo "NEXT_PUBLIC_API_URL=http://localhost:3000" >> .env.local
# Start development server
npm run dev
Testing
# Test backend proxy
curl http://localhost:3001/api/news
# Test React app
npm test
# Check for security issues
npm audit
Production Deployment
Environment Variables:# Backend server
BYUL_API_KEY=byul_api_key
# Frontend (if needed for API URL)
REACT_APP_API_URL=https://your-backend-api.com
- API key only in backend environment
- CORS properly configured
- Rate limiting on proxy endpoints
- Input validation on all parameters
- Error messages don’t expose internal details
CSS Styling Example
.news-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.news-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
background: white;
}
.news-item h3 {
margin: 0 0 8px 0;
}
.news-item h3 a {
color: #2563eb;
text-decoration: none;
}
.news-item h3 a:hover {
text-decoration: underline;
}
.importance-badge {
background: #f3f4f6;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 500;
}
.symbol-tag {
background: #3b82f6;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.75rem;
margin-right: 4px;
}
.status.connected {
color: #059669;
}
.status.disconnected {
color: #dc2626;
}
.sentiment.positive {
color: #059669;
}
.sentiment.negative {
color: #dc2626;
}
.sentiment.neutral {
color: #6b7280;
}