OS Trading Engine
Technical Documentation
Backend
API Layer & Middleware

API Layer & Middleware

The API layer handles HTTP requests through Express routes and middleware. All endpoints are versioned under /api/v1/.

API Structure

src/api/
├── index.ts              # Route aggregator
└── v1/
    ├── agents/           # Agent management
    ├── agent-balances/   # Balance queries
    ├── agent-positions/  # Position queries
    ├── agent-transactions/ # Transaction history
    ├── agent-historical-swaps/ # Completed trades
    ├── api-keys/         # API key management
    ├── auth/             # Authentication
    ├── data-sources/     # Price feed sources
    ├── health/           # Health checks
    ├── metrics/          # Prometheus metrics
    ├── price-feeds/      # Current prices
    ├── trading-signals/  # Signal management
    ├── wallets/          # Wallet operations
    └── webhooks/         # Webhook testing

Endpoint Reference

Authentication (/api/v1/auth)

MethodEndpointDescriptionAuth
POST/registerCreate new userNone
POST/loginAuthenticate userNone
POST/tokens/refreshRefresh access tokenRefresh token
GET/meGet current userJWT

Agents (/api/v1/agents)

MethodEndpointDescriptionAuth
POST/Create agentJWT
GET/List user's agentsJWT
GET/:idGet agent detailsJWT
PUT/:idUpdate agentJWT
DELETE/:idDelete agentJWT
GET/:id/configGet trading configJWT
PUT/:id/configUpdate trading configJWT
GET/:id/positionsGet open positionsJWT
GET/:id/performanceGet performance metricsJWT
GET/:id/balance-historyGet balance historyJWT

Trading Signals (/api/v1/trading-signals)

MethodEndpointDescriptionAuth
POST/Create signalAPI Key
GET/List signalsJWT
GET/:idGet signal detailsJWT
DELETE/:idDelete signalJWT
GET/exportExport signals CSVJWT

Wallets (/api/v1/wallets)

MethodEndpointDescriptionAuth
GET/List available walletsJWT
POST/assignAssign wallet to agentJWT
POST/resetReset simulation walletJWT
POST/check-depositsCheck for SOL depositsJWT

Health (/api/v1/health)

MethodEndpointDescriptionAuth
GET/Full health checkNone
GET/liveLiveness probeNone
GET/readyReadiness probeNone

Route Definition Pattern

Routes follow a consistent pattern with middleware chaining:

// src/api/v1/agents/routes.ts
import { Router } from 'express';
import { authenticate } from '@/middleware/auth.js';
import { validate } from '@/middleware/validation.js';
import { CreateAgentSchema, UpdateAgentSchema } from 'nexgent-open-source-trading-engine/shared';
 
const router = Router();
 
// Create agent: Auth → Validate → Handler
router.post('/', authenticate, validate(CreateAgentSchema), createAgent);
 
// List agents: Auth → Handler
router.get('/', authenticate, listAgents);
 
// Get agent: Auth → Handler
router.get('/:id', authenticate, getAgent);
 
// Update agent: Auth → Validate → Handler
router.put('/:id', authenticate, validate(UpdateAgentSchema), updateAgent);
 
// Delete agent: Auth → Handler
router.delete('/:id', authenticate, deleteAgent);
 
export default router;

Middleware

Authentication (auth.ts)

Verifies JWT tokens from Authorization header:

export function authenticate(
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
): void {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Authorization required' });
  }
  
  const token = authHeader.slice(7);
  
  try {
    const payload = verifyToken(token);
    
    // Check token type
    if (payload.type !== 'access') {
      return res.status(401).json({ error: 'Invalid token type' });
    }
    
    // Check blacklist
    if (payload.jti) {
      const isBlacklisted = await redisTokenService.isAccessTokenBlacklisted(payload.jti);
      if (isBlacklisted) {
        return res.status(401).json({ error: 'Token revoked' });
      }
    }
    
    req.user = { id: payload.userId, email: payload.email };
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

API Key Authentication (api-key-auth.ts)

Authenticates using API keys (for programmatic access):

export function authenticateApiKey(
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
): void {
  const apiKey = extractApiKey(req); // X-API-Key header or Bearer token
  
  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }
  
  const keyData = await verifyApiKey(apiKey);
  if (!keyData) {
    return res.status(401).json({ error: 'Invalid API key' });
  }
  
  req.user = { id: keyData.userId, email: keyData.email };
  req.apiKeyId = keyData.id;
  next();
}
 
// With scope requirement
export function authenticateApiKeyWithScope(requiredScope: string) {
  return async (req, res, next) => {
    // ... same as above, plus:
    if (!hasScope(keyData.scopes, requiredScope)) {
      return res.status(403).json({ error: `Missing scope: ${requiredScope}` });
    }
    next();
  };
}

Validation (validation.ts)

Validates request bodies using Zod schemas:

export function validate(schema: ZodSchema) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);
    
    if (!result.success) {
      return res.status(400).json({
        error: 'Validation failed',
        details: result.error.flatten(),
      });
    }
    
    req.body = result.data; // Replace with validated data
    next();
  };
}

Zod schemas are defined in nexgent-open-source-trading-engine/shared and shared between frontend and backend for consistent validation.

Rate Limiting (rate-limiter.ts)

Prevents abuse with per-IP rate limits:

export function rateLimiter(
  maxRequests: number = 10,
  windowMs: number = 60000 // 1 minute
) {
  return (req: Request, res: Response, next: NextFunction) => {
    const ip = req.ip || 'unknown';
    const key = `${ip}:${req.path}`;
    
    let entry = rateLimitStore.get(key);
    
    if (!entry || entry.resetTime < Date.now()) {
      entry = { count: 1, resetTime: Date.now() + windowMs };
      rateLimitStore.set(key, entry);
      return next();
    }
    
    entry.count++;
    
    if (entry.count > maxRequests) {
      const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
      return res.status(429).json({
        error: 'Too many requests',
        retryAfter,
      });
    }
    
    next();
  };
}
 
// Pre-configured limiters
export const walletRateLimiter = rateLimiter(5, 60000);
export const signalsApiKeyRateLimiter = apiKeyRateLimiter(120, 60000);

Request Logger (request-logger.ts)

Structured logging for all requests:

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  const requestId = req.headers['x-request-id'] || randomUUID();
  req.headers['x-request-id'] = requestId;
  
  const start = Date.now();
  
  res.on('finish', () => {
    logger.info({
      requestId,
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration: Date.now() - start,
      userAgent: req.headers['user-agent'],
    }, 'Request completed');
  });
  
  next();
}

Error Handler (error-handler.ts)

Global error handling:

export function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  logger.error({
    error: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
  }, 'Unhandled error');
  
  // Don't leak internal errors in production
  const message = process.env.NODE_ENV === 'production'
    ? 'Internal server error'
    : err.message;
  
  res.status(500).json({ error: message });
}
 
export function notFoundHandler(req: Request, res: Response) {
  res.status(404).json({ error: 'Not found' });
}

Handler Pattern

Handlers extract validated data and delegate to domain services:

// src/api/v1/agents/handlers/create.ts
export async function createAgent(
  req: AuthenticatedRequest,
  res: Response
): Promise<void> {
  try {
    const userId = req.user!.id;
    const { name, tradingMode } = req.body; // Validated by middleware
    
    // Delegate to domain service
    const agent = await agentService.createAgent({
      userId,
      name,
      tradingMode,
    });
    
    res.status(201).json(agent);
  } catch (error) {
    if (error instanceof AgentError) {
      res.status(400).json({ error: error.message });
      return;
    }
    throw error; // Let error handler catch it
  }
}

Response Format

All endpoints return consistent JSON responses:

Success

// Single resource
{ "id": "uuid", "name": "Agent 1", ... }
 
// List
{ "items": [...], "total": 10 }
 
// Action result
{ "success": true, "message": "..." }

Error

{
  "error": "Error message",
  "details": { ... }  // Optional validation details
}

CORS Configuration

const corsOptions = {
  origin: process.env.CORS_ORIGIN
    ? process.env.CORS_ORIGIN.split(',')
    : ['http://localhost:3000'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
};
 
app.use(cors(corsOptions));