# 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: ``` 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)** ```json { "data": {}, "meta": { "request_id": "", "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)** ```json { "error": { "code": "ERROR_CODE", "message": "Human-readable message" }, "meta": { "request_id": "" } } ``` **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/` | 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](#response-envelope)). **`data` payload (illustrative):** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "results": [ { "status": "ok", "result": { ... } }, { "status": "ok", "result": { ... } } ] } ``` --- ### Backtest #### `POST /api/portfolio/backtest` Run historical backtest. **Request:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "tickers": ["AAPL", "MSFT", "GOOGL"], "start_date": "2020-01-01", "end_date": "2024-12-31", "n_points": 20 } ``` **Response:** ```json { "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:** ```json { "tickers": [ { "symbol": "AAPL", "name": "Apple Inc.", "sector": "Technology" } ] } ``` --- ### Async Jobs #### `POST /api/jobs/optimize` Submit async optimization. Returns `job_id`. **Request:** ```json { "payload": { /* optimize request */ }, "webhook_url": "https://your-server/callback" } ``` #### `POST /api/jobs/backtest` Submit async backtest. Returns `job_id`. #### `GET /api/jobs/` 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:** ```json { "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):** ```json { "run_id": "", "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/` Fetch a single run. Returns spec, status, result (when completed), and error (when failed). 403 if the run belongs to a different tenant. **Response:** ```json { "id": "", "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: ```json { "error": { "code": "ERROR", "message": "Human-readable message" } } ``` HTTP status codes: 400 (validation), 401 (unauthorized), 429 (rate limit), 500 (server error). --- *Last updated: 2026-02*