TradAI API Reference¶
Complete API reference for TradAI services. For interactive documentation, visit the Swagger UI at each service's /docs endpoint.
Services Overview¶
| Service | Port | Description |
|---|---|---|
| Backend | 8000 | Central gateway for backtest orchestration, strategy management |
| Strategy Service | 8003 | Strategy configuration, MLflow integration, hyperopt, A/B testing |
| Data Collection | 8002 | Market data fetching and storage |
Authentication¶
All services support optional JWT authentication via AWS Cognito.
# Include token in Authorization header
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/v1/backtests
Configuration¶
| Variable | Description |
|---|---|
{SERVICE}_COGNITO_USER_POOL_ID | Cognito User Pool ID |
{SERVICE}_COGNITO_CLIENT_ID | Cognito App Client ID |
{SERVICE}_COGNITO_REGION | AWS region (default: us-east-1) |
Authentication is optional in development but enforced in production.
Response Headers¶
All responses include: - X-Correlation-ID: Unique request ID for tracing/debugging
Error Response Format¶
Validation Error (400)¶
{
"detail": [
{
"type": "missing",
"loc": ["body", "config", "strategy"],
"msg": "Field required",
"input": null
}
]
}
Not Found (404)¶
Service Unavailable (503)¶
Error Code Catalog¶
Standardized error codes for programmatic error handling.
Validation Errors (VAL)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| VAL001 | 400 | Invalid date range | Ensure start < end and dates are ISO 8601 format |
| VAL002 | 400 | Invalid symbol format | Use BASE/QUOTE:SETTLE format (e.g., BTC/USDT:USDT) |
| VAL003 | 400 | Invalid timeframe | Use valid timeframe: 1m, 5m, 15m, 1h, 4h, 1d |
| VAL004 | 400 | Missing required field | Check request body for required fields |
| VAL005 | 400 | Value out of range | Check min/max constraints in field documentation |
| VAL006 | 400 | Invalid config format | Validate JSON structure matches expected schema |
Authentication Errors (AUTH)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| AUTH001 | 401 | Token expired | Refresh JWT token via Cognito |
| AUTH002 | 401 | Invalid token | Verify token signature and issuer |
| AUTH003 | 401 | Missing token | Include Authorization: Bearer <token> header |
| AUTH004 | 403 | Insufficient permissions | Request elevated permissions or use different credentials |
| AUTH005 | 403 | Resource access denied | Verify resource ownership or sharing permissions |
Resource Errors (RES)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| RES001 | 404 | Backtest job not found | Verify job_id is correct and job exists |
| RES002 | 404 | Strategy not found | Check strategy name in /strategies endpoint |
| RES003 | 404 | Model version not found | List versions via /models/{name}/versions |
| RES004 | 404 | Config not found | Check available configs via /strategies |
| RES005 | 409 | Resource conflict | Resource is in a conflicting state (e.g., already terminal) |
| RES006 | 410 | Resource expired | Resource has been archived or deleted |
Service Errors (SVC)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| SVC001 | 500 | Internal server error | Check logs, report if persistent |
| SVC002 | 502 | Bad gateway | Upstream service returned invalid response |
| SVC003 | 503 | Strategy service unavailable | Wait and retry, check service health |
| SVC004 | 503 | Data collection unavailable | Wait and retry, check service health |
| SVC005 | 503 | MLflow unavailable | Wait and retry, check MLflow health |
| SVC006 | 504 | Gateway timeout | Increase timeout or retry later |
Data Errors (DATA)¶
| Code | Status | Description | Resolution |
|---|---|---|---|
| DATA001 | 400 | Insufficient historical data | Extend date range or use different symbol |
| DATA002 | 400 | Data gap detected | Use /data/freshness to check data availability |
| DATA003 | 404 | Symbol not available | Check /data/symbols for available symbols |
| DATA004 | 503 | Exchange API error | Retry later, check exchange status |
| DATA005 | 503 | Data storage unavailable | Check ArcticDB health |
Example Error Response¶
{
"detail": {
"code": "VAL001",
"message": "Invalid date range: start must be before end",
"field": "date_range",
"context": {
"start": "2024-03-01T00:00:00Z",
"end": "2024-01-01T00:00:00Z"
}
}
}
Error Handling Best Practices¶
import type { ErrorResponse } from './types/backend';
async function submitBacktest(config: BacktestConfig) {
const { data, error } = await client.POST('/api/v1/backtests', { body: config });
if (error) {
const errorCode = error.detail?.code;
switch (errorCode) {
case 'VAL001':
throw new Error('Please select a valid date range');
case 'AUTH001':
await refreshToken();
return submitBacktest(config); // Retry
case 'SVC003':
case 'SVC004':
await delay(5000);
return submitBacktest(config); // Retry with backoff
default:
throw new Error(`API Error: ${error.detail?.message || 'Unknown error'}`);
}
}
return data;
}
API Workflows¶
Backtest Lifecycle¶
┌──────────────────────────────────────┐
│ 1. Submit Backtest │
│ POST /backtests │
└────────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 2. Job Queued (SQS) │
│ Status: "pending" │
└────────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 3. ECS Task Started │
│ Status: "running" │
└────────────────┬─────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────────┐ ┌───────────────┐ ┌────────────────────┐
│ 4a. Completed │ │ 4b. Failed │ │ 4c. Cancelled │
│ Results in S3 │ │ Error logged │ │ User-initiated │
└────────────────────┘ └───────────────┘ └────────────────────┘
Step-by-Step Flow:
- Submit -
POST /backtestscreates job, returnsjob_id - Monitor - Poll
GET /backtests/{job_id}until terminal state - Results - On completion,
GET /backtests/{job_id}/equityfor charts - Report -
GET /backtests/{job_id}/report-datafor full analysis
Strategy Promotion Pipeline¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Backtest │ ──► │ Register │ ──► │ Stage │
│ Validation │ │ to MLflow │ │ (Dry-Run) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Deploy │ ◄── │ Promote │ ◄── │ A/B Test │
│ (Production) │ │ to Production │ │ (Optional) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
API Sequence:
# 1. Run backtest
POST /backtests → job_id
# 2. Wait for completion
GET /backtests/{job_id} → status: "completed"
# 3. Register to MLflow
POST /strategies/{name}/register
--backtest_run_id <mlflow_run_id>
--docker_image_uri <ecr_uri>
# 4. Stage for validation
POST /strategies/{name}/stage --version 3
# 5. (Optional) A/B test
POST /models/{name}/challenges
--champion_version 2
--challenger_version 3
# 6. Promote to production
POST /strategies/{name}/promote --version 3
Hyperopt Workflow¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Start Hyperopt │ ──► │ Monitor Trials │ ──► │ Get Results │
│ POST /hyperopt │ │ GET /hyperopt/ │ │ GET /hyperopt/ │
│ │ │ {id} │ │ {id}/result │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ Progress: 45/100 │
│ Best: 1.85 │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Live Updates │ │ Best Params │
│ (WebSocket) │ │ Apply Config │
└─────────────────┘ └─────────────────┘
Usage Example:
// 1. Start optimization
const { data: job } = await client.POST('/api/v1/hyperopt', {
body: {
strategy: 'TrendFollowingStrategy',
n_trials: 100,
objective_metric: 'sharpe_ratio'
}
});
// 2. Poll for progress
let status = 'running';
while (status === 'running') {
const { data } = await client.GET('/api/v1/hyperopt/{job_id}', {
params: { path: { job_id: job.job_id } }
});
status = data.status;
console.log(`Progress: ${data.current_trial}/${data.total_trials}`);
await delay(5000);
}
// 3. Get best parameters
const { data: result } = await client.GET('/api/v1/hyperopt/{job_id}/result', {
params: { path: { job_id: job.job_id } }
});
console.log('Best params:', result.best_params);
Champion/Challenger A/B Test¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Create Challenge│ ──► │ Route Traffic │ ──► │ Log Trades │
│ POST /challenges│ │ Canary: 5/95 │ │ POST /trades │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌───────────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ Canary Stages │
│ Stage 1: 5% ──► Stage 2: 10% ──► Stage 3: 25% ──► 50%│
└───────────────────────────────────────────────────────┘
│
▼
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ PROMOTE │ │ ROLLBACK │
│ Challenger │ │ Keep Champion │
│ Wins │ │ Challenger │
└───────────────┘ └───────────────┘
Evaluation Criteria:
| Metric | Threshold | Description |
|---|---|---|
| p_value | < 0.05 | Statistical significance |
| improvement_pct | > 10% | Meaningful improvement |
| min_trades | >= 100 | Sufficient sample size |
Backend Service (Port 8000)¶
Base URL: http://localhost:8000/api/v1
Health Check¶
GET /health¶
Check service health status with dependency checks.
Response 200:
{
"status": "healthy",
"service": "backend",
"version": "0.1.0",
"dependencies": {
"strategy_service": "healthy",
"data_collection": "healthy",
"mlflow": "healthy"
}
}
Backtest Management¶
POST /backtests¶
Submit a new backtest job.
Request:
{
"config": {
"strategy": "TrendFollowingStrategy",
"pairs": ["BTC/USDT:USDT", "ETH/USDT:USDT"],
"timeframe": "1h",
"date_range": {
"start": "2024-01-01T00:00:00Z",
"end": "2024-03-01T00:00:00Z"
},
"stake_amount": 100.0,
"dry_run_wallet": 10000.0
},
"experiment_name": "trend-following-btc-eth-q1-2024",
"priority": 5
}
Response 201:
Errors: 400 (validation), 503 (service unavailable)
GET /backtests¶
List backtest jobs with pagination.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | limit | int | 20 | Max results per page (1-100) | | cursor | string | null | Pagination cursor | | status | string | null | Filter by status |
Response 200:
{
"items": [
{
"job_id": "bt-abc123def456",
"status": "completed",
"result": { ... },
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:00Z"
}
],
"next_cursor": "eyJsYXN0X2lkIjogImJ0LXh5eiJ9"
}
GET /backtests/{job_id}¶
Get status of a specific backtest job.
Response 200:
{
"job_id": "bt-abc123def456",
"status": "completed",
"result": {
"total_trades": 150,
"winning_trades": 95,
"losing_trades": 55,
"total_profit": 2500.50,
"total_profit_pct": 25.05,
"sharpe_ratio": 1.85,
"profit_factor": 1.72,
"win_rate": 63.33,
"max_drawdown_pct": 12.5
},
"error": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:00Z"
}
Errors: 400 (invalid job_id), 404 (not found)
POST /backtests/{job_id}/cancel¶
Cancel a running backtest job.
Response 200:
Errors: 400 (already terminal), 404 (not found)
GET /backtests/{job_id}/equity¶
Get equity curve data for visualization.
Response 200:
{
"dates": ["2024-01-01", "2024-01-02", ...],
"equity": [10000.0, 10150.0, ...],
"drawdown": [0.0, 0.0, -1.2, ...],
"monthly_returns": {
"2024-01": 5.2,
"2024-02": 8.1
}
}
Errors: 400 (not completed), 404 (not found)
GET /backtests/{job_id}/report-data¶
Get complete report data for HTML report generation.
Response 200:
Strategy Management¶
GET /strategies¶
List available strategy configurations.
Response 200:
GET /strategies/{config_name}¶
Get a specific strategy configuration.
Response 200:
{
"strategy": "TrendFollowingStrategy",
"pairs": ["BTC/USDT:USDT"],
"timeframe": "1h",
"stake_amount": 100.0,
"freqai": {
"enabled": true,
"model_type": "LightGBMClassifier"
}
}
POST /strategies/{name}/stage¶
Stage a strategy model version for validation testing.
Request:
Response 200:
{
"success": true,
"message": "Version 3 staged successfully",
"previous_stage": "None",
"new_stage": "Staging"
}
POST /strategies/{name}/promote¶
Promote a strategy model version to Production.
Request:
Response 200:
{
"success": true,
"message": "Version 3 promoted to Production",
"archived_versions": ["2"],
"previous_stage": "Staging",
"new_stage": "Production"
}
Model Operations¶
GET /models/{model_name}/versions¶
List all versions of a registered model.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | include_archived | bool | true | Include archived versions |
Response 200:
{
"model_name": "TrendFollowingStrategy",
"versions": [
{
"version": "3",
"stage": "Production",
"created_at": "2024-01-15T10:30:00Z"
},
{
"version": "2",
"stage": "Archived",
"created_at": "2024-01-10T08:00:00Z"
}
],
"count": 2
}
POST /models/{model_name}/rollback¶
Rollback a model to a previous version.
Request:
Response 200:
{
"success": true,
"message": "Rolled back to version 2",
"previous_production": "3",
"new_production": "2"
}
Strategy Catalog¶
GET /catalog/strategies¶
List strategies from the catalog with filtering.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | stage | string | Filter by stage (Production, Staging) | | exchange | string | Filter by exchange | | timeframe | string | Filter by timeframe | | search | string | Text search | | min_sharpe | float | Minimum Sharpe ratio | | max_drawdown | float | Maximum drawdown % | | sort_field | string | Sort by (name, sharpe, drawdown) | | sort_order | string | asc or desc | | limit | int | Page size (1-100) | | offset | int | Page offset |
GET /catalog/strategies/{name}/compare¶
Compare two versions of a strategy.
Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | version_a | string | First version (champion) | | version_b | string | Second version (challenger) |
Response 200:
{
"decision": "PROMOTE",
"confidence": 0.85,
"metrics": {
"sharpe_ratio": {"a": 1.5, "b": 1.8, "improvement": 20.0},
"profit_factor": {"a": 1.6, "b": 1.9, "improvement": 18.75}
},
"recommendation": "Challenger outperforms champion with high confidence"
}
Data Proxy¶
GET /data/symbols¶
Get available trading symbols from exchange.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | exchange | string | binance_futures | Exchange key |
Response 200:
GET /data/freshness¶
Check data freshness for symbols.
Query Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | symbols | list | required | Symbols to check | | stale_threshold_hours | int | 24 | Stale threshold |
Strategy Service (Port 8003)¶
Base URL: http://localhost:8003/api/v1
Hyperparameter Optimization¶
POST /hyperopt¶
Start Optuna hyperparameter optimization.
Request:
{
"strategy": "TrendFollowingStrategy",
"pairs": ["BTC/USDT:USDT"],
"timeframe": "1h",
"n_trials": 100,
"train_period_days": 180,
"backtest_period_days": 30,
"objective_metric": "sharpe_ratio",
"direction": "maximize",
"model_types": ["LightGBMClassifier", "XGBClassifier"],
"n_estimators_range": [100, 1000],
"learning_rate_range": [0.01, 0.3]
}
Response 200:
{
"job_id": "abc12345",
"status": "pending",
"message": "Hyperopt job started with 100 trials",
"n_trials": 100,
"strategy": "TrendFollowingStrategy"
}
GET /hyperopt/{job_id}¶
Get hyperopt job status.
Response 200:
{
"job_id": "abc12345",
"status": "running",
"started_at": "2024-01-15T10:30:00Z",
"progress": 45,
"current_trial": 45,
"total_trials": 100,
"best_value": 1.85
}
GET /hyperopt/{job_id}/result¶
Get hyperopt results when complete.
Response 200:
{
"best_params": {
"model_type": "LightGBMClassifier",
"n_estimators": 500,
"learning_rate": 0.05
},
"best_value": 2.1,
"n_trials_completed": 100,
"study_name": "abc12345"
}
Champion/Challenger A/B Testing¶
POST /models/{model_name}/challenges¶
Create a champion/challenger A/B test.
Request:
{
"champion_version": "2",
"challenger_version": "3",
"min_trades": 100,
"max_duration_hours": 168,
"enable_canary": true
}
Response 201:
{
"challenge_id": "ch-abc123",
"model_name": "TrendFollowingStrategy",
"champion_version": "2",
"challenger_version": "3",
"status": "active",
"canary_stage": "stage_1",
"traffic_split": [0.95, 0.05]
}
POST /challenges/{challenge_id}/trades¶
Add a trade result to a challenge.
Request:
GET /challenges/{challenge_id}/evaluate¶
Evaluate challenge and get recommendation.
Response 200:
{
"challenge_id": "ch-abc123",
"status": "completed",
"recommendation": "PROMOTE_CHALLENGER",
"champion_mean": 1.2,
"challenger_mean": 1.8,
"improvement_pct": 50.0,
"p_value": 0.02,
"is_significant": true
}
Strategy Registration¶
POST /strategies/{name}/register¶
Register a strategy version after successful backtest.
Request:
{
"backtest_run_id": "run-abc123",
"docker_image_uri": "123456789.dkr.ecr.us-east-1.amazonaws.com/strategy:v1.0.0",
"description": "Improved trend following with ML enhancements",
"skip_validation": false,
"thresholds": {
"min_sharpe": 1.0,
"min_profit_factor": 1.2,
"max_drawdown": 20.0
}
}
Response 201:
{
"success": true,
"message": "Strategy registered successfully",
"version": "4",
"model_name": "TrendFollowingStrategy"
}
TypeScript Integration¶
Generate Types¶
# Start services first
just up
# Generate TypeScript types from running services
npm run generate-types
Client Setup¶
import createClient from 'openapi-fetch';
import type { paths } from './types/backend';
// Create client with default configuration
const client = createClient<paths>({
baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
headers: {
'Content-Type': 'application/json',
},
});
// Add authentication interceptor
client.use({
async onRequest({ request }) {
const token = await getAuthToken();
if (token) {
request.headers.set('Authorization', `Bearer ${token}`);
}
return request;
},
async onResponse({ response }) {
// Extract correlation ID for debugging
const correlationId = response.headers.get('X-Correlation-ID');
if (correlationId) {
console.debug(`Request ${correlationId}`);
}
return response;
},
});
Basic Usage¶
// Type-safe API calls
const { data, error } = await client.POST('/api/v1/backtests', {
body: {
config: {
strategy: 'TrendFollowingStrategy',
pairs: ['BTC/USDT:USDT'],
timeframe: '1h',
date_range: {
start: '2024-01-01T00:00:00Z',
end: '2024-03-01T00:00:00Z',
},
stake_amount: 100.0,
dry_run_wallet: 10000.0,
},
experiment_name: 'my-backtest',
priority: 5,
},
});
if (error) {
console.error('Failed:', error.detail);
return;
}
console.log('Job ID:', data.job_id);
Error Handling¶
import type { ErrorDetail } from './types/backend';
// Retry configuration
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
async function withRetry<T>(
fn: () => Promise<{ data?: T; error?: { detail: ErrorDetail } }>,
retries = MAX_RETRIES
): Promise<T> {
const { data, error } = await fn();
if (error) {
const code = error.detail?.code;
// Non-retryable errors
if (code?.startsWith('VAL') || code?.startsWith('AUTH')) {
throw new ApiError(error.detail);
}
// Retryable service errors
if (code?.startsWith('SVC') && retries > 0) {
await delay(RETRY_DELAY * (MAX_RETRIES - retries + 1));
return withRetry(fn, retries - 1);
}
throw new ApiError(error.detail);
}
return data!;
}
// Usage
try {
const result = await withRetry(() =>
client.POST('/api/v1/backtests', { body: config })
);
console.log('Created:', result.job_id);
} catch (e) {
if (e instanceof ApiError) {
handleApiError(e);
}
}
Pagination¶
// Generic paginated fetcher
async function* fetchPaginated<T>(
endpoint: string,
params?: Record<string, unknown>
): AsyncGenerator<T, void, unknown> {
let cursor: string | null = null;
do {
const { data, error } = await client.GET(endpoint, {
params: {
query: { ...params, cursor, limit: 50 },
},
});
if (error) throw new ApiError(error.detail);
for (const item of data.items) {
yield item as T;
}
cursor = data.next_cursor;
} while (cursor);
}
// Usage: Iterate all backtests
for await (const backtest of fetchPaginated('/api/v1/backtests')) {
console.log(backtest.job_id, backtest.status);
}
// Or collect all into array
async function getAllBacktests(status?: string) {
const results = [];
for await (const bt of fetchPaginated('/api/v1/backtests', { status })) {
results.push(bt);
}
return results;
}
Polling for Job Completion¶
interface PollOptions {
interval?: number;
timeout?: number;
onProgress?: (status: string) => void;
}
async function pollBacktest(
jobId: string,
options: PollOptions = {}
): Promise<BacktestResult> {
const { interval = 5000, timeout = 600000, onProgress } = options;
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const { data, error } = await client.GET('/api/v1/backtests/{job_id}', {
params: { path: { job_id: jobId } },
});
if (error) throw new ApiError(error.detail);
onProgress?.(data.status);
if (data.status === 'completed') {
return data.result;
}
if (data.status === 'failed') {
throw new Error(`Backtest failed: ${data.error}`);
}
if (data.status === 'cancelled') {
throw new Error('Backtest was cancelled');
}
await delay(interval);
}
throw new Error(`Polling timeout after ${timeout}ms`);
}
// Usage
const result = await pollBacktest(jobId, {
interval: 3000,
timeout: 300000,
onProgress: (status) => console.log(`Status: ${status}`),
});
Complete Example: Submit and Monitor Backtest¶
import createClient from 'openapi-fetch';
import type { paths, components } from './types/backend';
type BacktestConfig = components['schemas']['BacktestConfig'];
type BacktestResult = components['schemas']['BacktestResult'];
const client = createClient<paths>({
baseUrl: 'http://localhost:8000',
});
async function runBacktest(config: BacktestConfig): Promise<BacktestResult> {
// 1. Submit backtest
const { data: job, error: submitError } = await client.POST('/api/v1/backtests', {
body: {
config,
experiment_name: `backtest-${Date.now()}`,
priority: 5,
},
});
if (submitError) {
throw new Error(`Submit failed: ${submitError.detail}`);
}
console.log(`Job submitted: ${job.job_id}`);
// 2. Poll until complete
const result = await pollBacktest(job.job_id, {
onProgress: (status) => console.log(`[${job.job_id}] ${status}`),
});
// 3. Get equity curve
const { data: equity } = await client.GET('/api/v1/backtests/{job_id}/equity', {
params: { path: { job_id: job.job_id } },
});
return { ...result, equity };
}
// Run it
const result = await runBacktest({
strategy: 'TrendFollowingStrategy',
pairs: ['BTC/USDT:USDT', 'ETH/USDT:USDT'],
timeframe: '1h',
date_range: {
start: '2024-01-01T00:00:00Z',
end: '2024-06-01T00:00:00Z',
},
stake_amount: 100,
dry_run_wallet: 10000,
});
console.log(`Sharpe: ${result.sharpe_ratio}, Profit: ${result.total_profit_pct}%`);
Rate Limiting¶
Rate limiting is not currently implemented. Consider implementing for production: - 100 requests/minute for authenticated users - 20 requests/minute for unauthenticated users
Versioning¶
API versioning is done via URL path (/api/v1/). Breaking changes will increment the version number.
Support¶
- Documentation:
/docson each service - Health Check:
/api/v1/healthon each service - Issues: https://github.com/tradai/tradai-uv/issues