@podx/api
v2.0.2
Published
π REST API server for PODx - Twitter/X scraping and crypto analysis platform
Maintainers
Readme
@podx/api
The API package provides a high-performance REST API server for the PODx ecosystem, built with Hono and optimized for Bun runtime. It offers endpoints for scraping data, signal analysis, token information, and system health monitoring.
π¦ Installation
# Install from workspace
bun add @podx/api@workspace:*
# Or install from npm (when published)
bun add @podx/apiποΈ Architecture
The API package is built with modern web standards and optimized for performance:
packages/api/src/
βββ server/ # Server implementation
β βββ index.ts # Main server setup with Hono
βββ routes/ # API route handlers
β βββ index.ts # Route setup and configuration
β βββ health.ts # Health check endpoints
β βββ signals.ts # Signal analysis endpoints
β βββ tokens.ts # Token information endpoints
β βββ performance.ts # Performance monitoring
βββ auth/ # Authentication system
β βββ index.ts # JWT token utilities
β βββ jwt.ts # JWT implementation
β βββ middleware.ts # Auth middleware
βββ middleware/ # Custom middleware
βββ validation/ # Request validation schemas
βββ types/ # TypeScript type definitions
βββ index.ts # Main exportsπ Quick Start
import { PodxAPIServer } from '@podx/api';
// Create and start the server
const server = new PodxAPIServer();
const instance = server.start(3000);
console.log('π API server running on http://localhost:3000');
// Generate community tokens
const token = await server.generateCommunityToken(
'user-123',
'premium',
'7d'
);
console.log('Generated token:', token);
// Stop the server
server.stop();π Authentication
The API uses JWT-based authentication with tiered access control:
JWT Token Generation
import { generateToken, verifyToken } from '@podx/api/auth';
// Generate a token for a user
const token = generateToken({
sub: 'user-123',
tier: 'premium'
}, '7d');
console.log('JWT Token:', token);
// Verify a token
const payload = verifyToken(token);
if (payload) {
console.log('User ID:', payload.sub);
console.log('Tier:', payload.tier);
}Tier-Based Access Control
// Free tier: Limited access
const freeToken = await server.generateCommunityToken('user-123', 'free', '1d');
// Premium tier: Enhanced features
const premiumToken = await server.generateCommunityToken('user-456', 'premium', '7d');
// Admin tier: Full access
const adminToken = await server.generateCommunityToken('admin-789', 'admin', '30d');Rate Limiting
The API implements tier-based rate limiting:
import { rateLimits } from '@podx/api/auth';
// Rate limit configurations
console.log('Free tier:', rateLimits.free); // 100 requests/hour
console.log('Premium tier:', rateLimits.premium); // 1000 requests/hour
console.log('Admin tier:', rateLimits.admin); // 10000 requests/hourπ‘ API Endpoints
Health & Status
GET /api/v1/health
Get server health status.
Response:
{
"data": {
"status": "healthy",
"timestamp": "2024-01-01T12:00:00.000Z",
"uptime": 3600,
"version": "2.0.0"
},
"meta": {
"requestId": "req-123",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}GET /api/v1/ready
Check if the server is ready to handle requests.
Response:
{
"data": {
"ready": true
},
"meta": {
"requestId": "req-124",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}Signal Analysis
GET /api/v1/signals/top
Get top trading signals (requires authentication).
Headers:
Authorization: Bearer <jwt-token>Query Parameters:
limit(optional): Number of signals to return (default: 5 for free, 20 for premium)offset(optional): Pagination offset (default: 0)
Response:
{
"data": {
"signals": [
{
"token": "BTC",
"type": "BUY",
"strength": 8,
"confidence": 85,
"reason": "Strong bullish sentiment detected",
"timeframe": "4h",
"timestamp": "2024-01-01T12:00:00.000Z",
"source": "PODX AI",
"riskLevel": "low"
}
],
"tier": "premium",
"pagination": {
"limit": 20,
"offset": 0,
"total": 150,
"hasMore": true
}
},
"meta": {
"requestId": "req-125",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}GET /api/v1/signals
Get paginated list of signals with filtering.
Query Parameters:
limit: Number of signals (1-100, default: 10)offset: Pagination offset (default: 0)token: Filter by token symboltype: Filter by signal type (BUY, SELL, HOLD)minConfidence: Minimum confidence level (0-100)
Token Information
GET /api/v1/tokens/trending
Get trending cryptocurrency tokens.
Query Parameters:
limit: Number of tokens (default: 10)timeframe: Time period (1h, 24h, 7d, default: 24h)
Response:
{
"data": {
"tokens": [
{
"symbol": "BTC",
"name": "Bitcoin",
"price": 45000,
"change24h": 2.5,
"volume24h": 28000000000,
"marketCap": 880000000000,
"mentions": 1250,
"sentiment": "bullish",
"lastUpdated": "2024-01-01T12:00:00.000Z"
}
],
"timeframe": "24h"
},
"meta": {
"requestId": "req-126",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}GET /api/v1/tokens/{symbol}
Get detailed information about a specific token.
Response:
{
"data": {
"token": {
"symbol": "BTC",
"name": "Bitcoin",
"contractAddress": "0x...",
"blockchain": "bitcoin",
"price": 45000,
"change24h": 2.5,
"volume24h": 28000000000,
"marketCap": 880000000000,
"supply": {
"circulating": 19000000,
"total": 21000000,
"max": 21000000
},
"socialMetrics": {
"twitterMentions": 1250,
"redditMentions": 450,
"sentiment": "bullish"
},
"lastUpdated": "2024-01-01T12:00:00.000Z"
}
}
}Performance Monitoring
GET /api/v1/performance/metrics
Get system performance metrics.
Response:
{
"data": {
"cpu": {
"usage": 45.2,
"cores": 8
},
"memory": {
"used": 1024,
"total": 8192,
"percentage": 12.5
},
"requests": {
"total": 15420,
"perSecond": 12.5,
"averageResponseTime": 245
},
"scraping": {
"activeJobs": 3,
"completedToday": 1250,
"averageProcessingTime": 890
}
}
}π§ Advanced Usage
Custom Server Configuration
import { createServer } from '@podx/api/server';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
// Create custom server with additional middleware
const app = createServer();
// Add custom middleware
app.use('*', cors({
origin: ['https://podx.dev', 'https://app.podx.dev'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
}));
// Add custom routes
app.get('/api/v1/custom', (c) => {
return c.json({
message: 'Custom endpoint',
timestamp: new Date().toISOString()
});
});
// Start the server
const server = Bun.serve({
port: 3000,
fetch: app.fetch
});Request Validation
import { z } from 'zod';
import { createServer } from '@podx/api/server';
// Custom validation schema
const CreateSignalSchema = z.object({
token: z.string().min(2).max(10),
type: z.enum(['BUY', 'SELL', 'HOLD']),
confidence: z.number().min(0).max(100),
reason: z.string().min(10).max(500)
});
const app = createServer();
// Route with validation
app.post('/api/v1/signals', async (c) => {
try {
const body = await c.req.json();
const validatedData = CreateSignalSchema.parse(body);
// Process the validated data
return c.json({
success: true,
signal: {
id: crypto.randomUUID(),
...validatedData,
createdAt: new Date().toISOString()
}
});
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Validation failed',
details: error.errors
}, 400);
}
return c.json({
error: 'Internal server error'
}, 500);
}
});Authentication Middleware
import { verifyToken, extractBearerToken } from '@podx/api/auth';
import { createServer } from '@podx/api/server';
// Create server with authentication
const app = createServer();
// Protected route middleware
const authenticate = async (c: any, next: any) => {
const authHeader = c.req.header('Authorization');
const token = extractBearerToken(authHeader);
if (!token) {
return c.json({ error: 'No token provided' }, 401);
}
const payload = verifyToken(token);
if (!payload) {
return c.json({ error: 'Invalid token' }, 401);
}
// Add user to context
c.set('user', payload);
await next();
};
// Protected route
app.get('/api/v1/user/profile', authenticate, (c) => {
const user = c.get('user');
return c.json({
profile: {
id: user.sub,
tier: user.tier,
// ... other profile data
}
});
});Error Handling
import { createServer } from '@podx/api/server';
// Custom error handler
const app = createServer();
// Override default error handler
app.onError((err, c) => {
console.error('API Error:', err);
// Custom error response based on error type
if (err instanceof ValidationError) {
return c.json({
error: {
type: 'validation_error',
message: err.message,
fields: err.fields
}
}, 400);
}
if (err instanceof AuthenticationError) {
return c.json({
error: {
type: 'authentication_error',
message: 'Authentication required'
}
}, 401);
}
// Default error response
return c.json({
error: {
type: 'internal_error',
message: 'An unexpected error occurred'
}
}, 500);
});
// Custom not found handler
app.notFound((c) => {
return c.json({
error: {
type: 'not_found',
message: 'Endpoint not found'
}
}, 404);
});π Integration with Scraper
import { ScraperService } from '@podx/scraper';
import { createServer } from '@podx/api/server';
const scraper = new ScraperService();
const app = createServer();
// API endpoint that uses scraper
app.post('/api/v1/scrape', async (c) => {
try {
const { username, maxTweets } = await c.req.json();
const tweets = await scraper.scrapeAccount({
targetUsername: username,
maxTweets: maxTweets || 100
});
return c.json({
success: true,
data: {
username,
tweetCount: tweets.length,
tweets: tweets.slice(0, 10) // Return first 10
}
});
} catch (error) {
console.error('Scraping error:', error);
return c.json({
error: 'Failed to scrape tweets'
}, 500);
}
});
// Real-time scraping endpoint
app.get('/api/v1/scrape/stream', (c) => {
return new Response(
new ReadableStream({
start(controller) {
scraper.scrapeAccount({
targetUsername: 'cryptowhale',
maxTweets: 100,
progressCallback: (progress) => {
const data = `data: ${JSON.stringify(progress)}\n\n`;
controller.enqueue(new TextEncoder().encode(data));
}
}).then(() => {
controller.close();
});
}
}),
{
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
}
}
);
});π Monitoring and Analytics
Request Logging
import { createServer } from '@podx/api/server';
import { logger } from '@podx/core';
const app = createServer();
// Custom logging middleware
app.use('*', async (c, next) => {
const start = Date.now();
const method = c.req.method;
const url = c.req.url;
logger.info('Request started', {
method,
url,
userAgent: c.req.header('User-Agent'),
ip: c.req.header('X-Forwarded-For')
});
await next();
const duration = Date.now() - start;
const status = c.res.status;
logger.info('Request completed', {
method,
url,
status,
duration,
size: c.res.headers.get('Content-Length')
});
});Performance Monitoring
import { createServer } from '@podx/api/server';
const app = createServer();
// Performance monitoring middleware
app.use('*', async (c, next) => {
const start = performance.now();
await next();
const duration = performance.now() - start;
// Log slow requests
if (duration > 1000) {
console.warn('Slow request detected', {
method: c.req.method,
url: c.req.url,
duration: `${duration.toFixed(2)}ms`
});
}
// Add performance header
c.header('X-Response-Time', `${duration.toFixed(2)}ms`);
});π§ͺ Testing
Unit Tests
import { describe, test, expect, mock } from 'bun:test';
import { createServer } from '@podx/api/server';
describe('API Server', () => {
test('should respond to health check', async () => {
const app = createServer();
const response = await app.request('/api/v1/health');
const data = await response.json();
expect(response.status).toBe(200);
expect(data.data.status).toBe('healthy');
expect(data.data).toHaveProperty('timestamp');
});
test('should handle authentication', async () => {
const app = createServer();
// Mock authentication middleware
app.use('/api/v1/protected/*', async (c, next) => {
const auth = c.req.header('Authorization');
if (!auth?.startsWith('Bearer ')) {
return c.json({ error: 'Unauthorized' }, 401);
}
await next();
});
app.get('/api/v1/protected/data', (c) => {
return c.json({ data: 'secret' });
});
// Test without auth
const response1 = await app.request('/api/v1/protected/data');
expect(response1.status).toBe(401);
// Test with auth
const response2 = await app.request('/api/v1/protected/data', {
headers: { Authorization: 'Bearer fake-token' }
});
expect(response2.status).toBe(200);
});
});Integration Tests
import { describe, test, expect } from 'bun:test';
import { PodxAPIServer } from '@podx/api';
describe('API Integration', () => {
let server: PodxAPIServer;
let port: number;
beforeAll(() => {
server = new PodxAPIServer();
port = 3001;
server.start(port);
});
afterAll(() => {
server.stop();
});
test('should handle full request flow', async () => {
// Generate token
const token = await server.generateCommunityToken('test-user', 'premium', '1h');
// Make authenticated request
const response = await fetch(`http://localhost:${port}/api/v1/signals/top`, {
headers: {
Authorization: `Bearer ${token}`
}
});
expect(response.ok).toBe(true);
const data = await response.json();
expect(data).toHaveProperty('data');
expect(data).toHaveProperty('meta');
});
});π API Response Format
All API responses follow a consistent format:
Success Response
{
"data": {
// Response data
},
"meta": {
"requestId": "req-123",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}Error Response
{
"error": {
"type": "validation_error",
"title": "Invalid Request Parameters",
"status": 400,
"detail": "The provided parameters are invalid",
"instance": "req-123"
},
"meta": {
"requestId": "req-123",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}π§ Configuration
Environment Variables
# Server Configuration
PORT=3000
HOST=0.0.0.0
# JWT Configuration
JWT_SECRET=your-secret-key-here
# Database Configuration
DATABASE_URL=postgresql://user:pass@localhost:5432/podx
# Scraper Configuration
XSERVE_USERNAME=your_twitter_username
XSERVE_PASSWORD=your_twitter_password
[email protected]Server Options
import { PodxAPIServer } from '@podx/api';
// Advanced server configuration
const server = new PodxAPIServer();
// Custom server options
const instance = server.start(3000, {
development: true,
cors: {
origin: ['https://podx.dev'],
credentials: true
},
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
}
});π€ Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
π License
This package is licensed under the ISC License. See the LICENSE file for details.
π Related Packages
- @podx/core - Core utilities and types
- @podx/scraper - Twitter scraping functionality
- @podx/cli - Command-line interface
- podx - Main CLI application
π Support
For support and questions:
- π§ Email: [email protected]
- π¬ Discord: PODx Community
- π Documentation: docs.podx.dev/api
- π Issues: GitHub Issues
