API Reference
The Quantum Hybrid Portfolio API is a Flask REST API running on port 5000 by default.
Base URL
- Local:
http://localhost:5000 - Health:
GET /api/health
Authentication
Optional API key via header:
X-API-Key: <your-api-key>
Set API_KEY environment variable on the server for static key validation.
When API_KEY_REQUIRED=true, clients must send X-API-Key on protected routes. GET /api/health and GET /metrics remain usable for probes without a key (see route decorators in api.py).
Response envelope
Most JSON routes wrap payloads in a standard envelope (success_response / error_response in api.py):
Success (2xx)
{
"data": {},
"meta": {
"request_id": "<uuid>",
"duration_ms": 12.34
}
}
The Next.js client (web/src/lib/api.ts) and CRA client (frontend/src/services/api.js) unwrap data in a response interceptor, so application code typically sees the inner payload only.
Error (4xx / 5xx)
{
"error": { "code": "ERROR_CODE", "message": "Human-readable message" },
"meta": { "request_id": "<uuid>" }
}
Exceptions: GET /metrics returns Prometheus text (no JSON envelope). Machine-readable contract: GET /api/docs/openapi.
Web client contract (migration Phase 1)
Must-not-break for the Next.js app (web/) and CRA dashboard (frontend/): changing method, path, auth requirement, or the shape of the unwrapped data payload for these routes requires coordination (version bump, dual support, or client updates). Automated checks: scripts/test_api_integration.py, tests/test_api_integration.py.
| Endpoint | Method | Auth | Notes |
|---|---|---|---|
/api/health |
GET | No key required | Rate limit exempt. |
/api/config/objectives |
GET | If API_KEY_REQUIRED |
Optimization objective list. |
/api/config/presets |
GET | If required | Preset configurations. |
/api/config/constraints |
GET | If required | Constraint schema. |
/api/config/ibm-quantum |
POST | If required | Body: { "token": "..." }. |
/api/config/ibm-quantum |
DELETE | If required | Clears stored token. |
/api/config/ibm-quantum/status |
GET | If required | Connection status. |
/api/config/ibm-quantum/workloads |
GET | Yes | Query limit (default 20, max 100). Lists IBM Runtime jobs for the tenant token. |
/api/market-data |
POST | If required | Historical returns / covariance inputs. |
/api/portfolio/optimize |
POST | If required | Single optimization run. |
/api/portfolio/optimize/batch |
POST | If required | Batch optimize. |
/api/portfolio/backtest |
POST | If required | Backtest. |
/api/portfolio/backtest/batch |
POST | If required | Batch backtest. |
/api/portfolio/efficient-frontier |
POST | If required | Efficient frontier points. |
/api/runs |
POST | If required | Create a durable lab run (async optimization). Returns run_id. |
/api/runs |
GET | If required | List recent lab runs for the tenant. Query limit (default 20). |
/api/runs/<id> |
GET | If required | Fetch run status, spec, and result (tenant-scoped). |
Environment variables (Flask server vs Next.js)
| Variable | Where | Purpose |
|---|---|---|
PORT |
Flask (server) | API listen port (default 5000). |
API_KEY, API_KEY_REQUIRED |
Flask (server) | Static API key validation. |
DATABASE_URL, REDIS_*, etc. |
Flask (server) | Backend only; never expose to the browser bundle. |
NEXT_PUBLIC_API_URL |
Next.js (build-time / browser) | Base URL for the Flask API (e.g. http://127.0.0.1:5000). Empty string = same origin (use when a dev proxy forwards /api to Flask). |
NEXT_PUBLIC_API_KEY |
Next.js (browser) | Optional; sent as X-API-Key if set. Treat as sensitive if the app is public; prefer same-origin proxy and server-side secrets for production. |
Copy .env.example to .env for Flask. For the Next app, use web/.env.local (see Next.js docs); only NEXT_PUBLIC_* variables are embedded in client bundles.
Endpoints
Health
GET /api/health
Returns API health and dependency checks. Uses the standard envelope; below is the shape of data after unwrapping (see Response envelope).
data payload (illustrative):
{
"status": "healthy",
"checks": { "api": "ok", "market_data": "available" },
"details": { "version": "1.0.0", "timestamp": "2026-02-15T12:00:00Z" },
"cache_entries": 0,
"message": "Quantum Portfolio Backend is running"
}
Market Data
POST /api/market-data
Fetch historical market data for given tickers.
Request:
{
"tickers": ["AAPL", "MSFT", "GOOGL"],
"start_date": "2020-01-01",
"end_date": "2024-12-31"
}
Response: Returns returns matrix, covariance, asset names, dates.
Portfolio Optimization
POST /api/portfolio/optimize
Run QSW portfolio optimization.
Request:
{
"tickers": ["AAPL", "MSFT", "GOOGL"],
"start_date": "2020-01-01",
"end_date": "2024-12-31",
"objective": "max_sharpe",
"omega": 0.3,
"evolution_time": 10,
"max_weight": 0.1,
"turnover_limit": 0.2,
"regime": "normal",
"strategy_preset": "balanced",
"constraints": {
"max_weight": 0.1,
"turnover_limit": 0.2,
"min_weight": 0.001
},
"target_return": 0.08
}
Parameters:
| Field | Type | Description |
|---|---|---|
tickers |
string[] | Asset symbols |
start_date |
string | YYYY-MM-DD |
end_date |
string | YYYY-MM-DD |
objective |
string | max_sharpe, min_variance, risk_parity, hrp, target_return |
omega |
number | Quantum mixing (0.05–0.6) |
evolution_time |
number | Evolution steps |
max_weight |
number | Max position (e.g. 0.1 = 10%) |
turnover_limit |
number | Max turnover per rebalance |
regime |
string | normal, bull, bear, volatile |
strategy_preset |
string | balanced, conservative, etc. |
target_return |
number | Required when objective=target_return |
Response:
{
"qsw_result": {
"weights": [0.05, 0.12, ...],
"sharpe_ratio": 1.45,
"expected_return": 0.12,
"volatility": 0.15,
"n_active": 18
},
"holdings": [
{ "name": "AAPL", "weight": 0.05, "sector": "Technology" }
],
"benchmarks": { ... },
"assets": [ ... ]
}
Batch Optimization
POST /api/portfolio/optimize/batch
Run multiple optimizations in one request.
Request:
{
"requests": [
{ "tickers": ["AAPL", "MSFT"], "start_date": "2020-01-01", "end_date": "2024-12-31", "omega": 0.2 },
{ "tickers": ["AAPL", "MSFT"], "start_date": "2020-01-01", "end_date": "2024-12-31", "omega": 0.4 }
],
"stop_on_error": false
}
Response:
{
"results": [
{ "status": "ok", "result": { ... } },
{ "status": "ok", "result": { ... } }
]
}
Backtest
POST /api/portfolio/backtest
Run historical backtest.
Request:
{
"tickers": ["AAPL", "MSFT", "GOOGL"],
"start_date": "2020-01-01",
"end_date": "2024-12-31",
"objective": "max_sharpe",
"strategy_preset": "balanced",
"rebalance_frequency": "monthly",
"constraints": { "max_weight": 0.1 }
}
Response:
{
"total_return": 0.45,
"annual_return": 0.12,
"sharpe_ratio": 1.2,
"max_drawdown": 0.15,
"volatility": 0.18,
"equity_curve": [
{ "date": "2020-01-02", "portfolio_value": 100000 },
...
]
}
Batch Backtest
POST /api/portfolio/backtest/batch
Run multiple backtests (e.g. for Scenario Tester).
Request:
{
"requests": [
{ "tickers": ["SPY"], "start_date": "2020-01-01", "end_date": "2024-12-31" },
{ "tickers": ["QQQ"], "start_date": "2020-01-01", "end_date": "2024-12-31" }
],
"stop_on_error": false
}
Efficient Frontier
POST /api/portfolio/efficient-frontier
Compute efficient frontier points.
Request:
{
"tickers": ["AAPL", "MSFT", "GOOGL"],
"start_date": "2020-01-01",
"end_date": "2024-12-31",
"n_points": 20
}
Response:
{
"frontier_points": [
{ "volatility": 0.12, "target_return": 0.08 },
...
]
}
Configuration
GET /api/config/objectives
Returns available optimization objectives.
GET /api/config/constraints
Returns constraint schema (max_weight, turnover_limit, etc.).
GET /api/config/presets
Returns strategy presets (balanced, conservative, etc.).
Ticker Search
GET /api/tickers/search?q=AAP
Search ticker catalog (autocomplete).
Response:
{
"tickers": [
{ "symbol": "AAPL", "name": "Apple Inc.", "sector": "Technology" }
]
}
Async Jobs
POST /api/jobs/optimize
Submit async optimization. Returns job_id.
Request:
{
"payload": { /* optimize request */ },
"webhook_url": "https://your-server/callback"
}
POST /api/jobs/backtest
Submit async backtest. Returns job_id.
GET /api/jobs/<job_id>
Poll job status. Returns status (pending/running/completed/failed) and result when completed.
Lab Runs (durable experiment registry)
Lab runs persist experiment specs and results to SQLite so users can leave the Portfolio Lab and return later to view a report.
POST /api/runs
Create a durable lab run. Enqueues async optimization and returns immediately.
Request:
{
"payload": {
"returns": [...],
"covariance": [[...]],
"asset_names": ["AAPL", "MSFT"],
"objective": "hybrid",
"weight_min": 0.005,
"weight_max": 0.30,
"seed": 42,
"data_mode": "synthetic",
"regime": "normal"
}
}
Response (202):
{ "run_id": "<uuid>", "status": "queued" }
GET /api/runs
List recent lab runs for the authenticated tenant.
| Query param | Default | Notes |
|---|---|---|
limit |
20 | Max 100. |
Response: { "runs": [...], "count": N }
GET /api/runs/<id>
Fetch a single run. Returns spec, status, result (when completed), and error (when failed). 403 if the run belongs to a different tenant.
Response:
{
"id": "<uuid>",
"tenant_id": "default",
"status": "completed",
"execution_kind": "async_optimize",
"spec": { "objective": "hybrid", "weight_min": 0.005, "..." : "..." },
"result": { "sharpe_ratio": 1.23, "weights": [...], "holdings": [...] },
"error": null,
"created_at": "2026-03-24T...",
"started_at": "...",
"finished_at": "..."
}
Metrics
GET /metrics
Prometheus metrics (request counts, latencies, optimization duration).
Error Responses
Errors return:
{
"error": {
"code": "ERROR",
"message": "Human-readable message"
}
}
HTTP status codes: 400 (validation), 401 (unauthorized), 429 (rate limit), 500 (server error).
Last updated: 2026-02